La forma más rápida de comparar dos listas genéricas de diferencias

214

¿Cuál es el más rápido (y menos intensivo en recursos) para comparar dos masivos (> 50,000 artículos) y como resultado tener dos listas como las siguientes:

  1. elementos que aparecen en la primera lista pero no en la segunda
  2. elementos que aparecen en la segunda lista pero no en la primera

Actualmente estoy trabajando con List o IReadOnlyCollection y resuelvo este problema en una consulta linq:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

Pero esto no funciona tan bien como me gustaría. ¿Alguna idea de hacer esto más rápido y menos intensivo en recursos ya que necesito procesar muchas listas?

Franco
fuente

Respuestas:

454

Uso Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

Sospecho que hay enfoques que en realidad serían marginalmente más rápidos que esto, pero incluso esto será mucho más rápido que su enfoque O (N * M).

Si desea combinar estos, puede crear un método con el anterior y luego una declaración de devolución:

return !firstNotSecond.Any() && !secondNotFirst.Any();

Un punto a destacar es que no es una diferencia en los resultados entre el código original de la pregunta y la solución aquí: todos los elementos duplicados que son sólo en una lista sólo se informará una vez con mi código, mientras que estarían reportados como muchos veces como ocurren en el código original.

Por ejemplo, con listas de [1, 2, 2, 2, 3]y [1], los "elementos en lista1 pero no en lista2" dan como resultado el código original [2, 2, 2, 3]. Con mi código simplemente sería [2, 3]. En muchos casos eso no será un problema, pero vale la pena tenerlo en cuenta.

Jon Skeet
fuente
8
¡Esto es realmente una gran ganancia de rendimiento! Gracias por esta respuesta
Frank
2
Me pregunto por dos grandes listas, ¿es útil ordenar antes de comparar? o dentro del método de extensión Excepto, la lista pasada ya está ordenada.
Larry
99
@ Larry: no está ordenado; Construye un conjunto de hash.
Jon Skeet
2
@PranavSingh: funcionará para cualquier cosa que tenga la igualdad adecuada, por lo que si su tipo personalizado anula Equals(object)y / o implementa IEquatable<T>, debería estar bien.
Jon Skeet
2
@ k2ibegin: utiliza el comparador de igualdad predeterminado, que utilizará una IEquatable<T>implementación o el object.Equals(object)método. Parece que debería crear una nueva pregunta con un ejemplo reproducible mínimo : realmente no podemos diagnosticar cosas en los comentarios.
Jon Skeet
40

Más eficiente sería usar Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

Este método se implementa mediante la ejecución diferida. Eso significa que podrías escribir, por ejemplo:

var first10 = inListButNotInList2.Take(10);

También es eficiente ya que internamente usa a Set<T>para comparar los objetos. Funciona al recopilar primero todos los valores distintos de la segunda secuencia y luego transmitir los resultados de la primera, verificando que no se hayan visto antes.

Tim Schmelter
fuente
1
Hmm No del todo diferido. Yo diría parcialmente diferido. Un completo Set<T>se construye a partir de la segunda secuencia (es decir, está completamente iterado y almacenado), luego se obtienen los elementos que se pueden agregar desde la primera secuencia.
gastador
2
@spender, eso es como decir que la ejecución de Whereestá parcialmente diferida porque en list.Where(x => x.Id == 5)el valor del número 5se almacena al inicio, en lugar de ejecutarse perezosamente.
jwg
27

Enumerable Secuencia Método igual

Determina si dos secuencias son iguales de acuerdo con un comparador de igualdad. MS.Docs

Enumerable.SequenceEqual(list1, list2);

Esto funciona para todos los tipos de datos primitivos. Si necesita usarlo en objetos personalizados, debe implementarIEqualityComparer

Define métodos para admitir la comparación de objetos para la igualdad.

Interfaz IEqualityComparer

Define métodos para admitir la comparación de objetos para la igualdad. MS.Docs para IEqualityComparer

miguelmpn
fuente
Esta debería ser la respuesta aceptada. La pregunta no es sobre SETS sino sobre LISTAS, que pueden contener duplicación de elementos.
Adrian Nasui
3
No veo cómo esta podría ser la respuesta, dado que el resultado SequenceEquales simple bool. El OP quiere dos listas de resultados, y describe lo que quieren en términos de operaciones establecidas: "elementos que aparecen en la primera lista pero no en la segunda". No hay indicios de que el pedido sea relevante, mientras que SequenceEqual lo considera relevante. Esto parece estar respondiendo una pregunta completamente diferente.
Jon Skeet
Sí, correcto, parece que respondí éste demasiado rápido y no se veía en la segunda parte de la solicitud ... igual que los dos primeros comentarios ...
miguelmpn
9

Si desea que los resultados no distingan entre mayúsculas y minúsculas , funcionará lo siguiente:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondcontendría b1.dll

secondNotFirstcontendría b2.dll

por ejemplo
fuente
5

¡No para este problema, pero aquí hay un código para comparar listas para igual y no! objetos idénticos:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}
Ermitaño Pío
fuente
1
Esto es lo que necesita para poder comparar tipos de datos personalizados. Luego useExcept
Pranav Singh
Probablemente puedas hacerlo mejor con tipos ordenables. Esto se ejecuta en O (n ^ 2), mientras que puedes hacer O (nlogn).
yuvalm2
3

intenta de esta manera:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
Ali Issa
fuente
13
Esto sufre de un rendimiento horrible, que requiere un escaneo de la segunda lista para cada elemento del primero. No hace downvoting porque funciona, pero es tan malo como el código original.
gastador
3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

A veces solo necesitas saber si dos listas son diferentes, y no cuáles son esas diferencias. En ese caso, considere agregar este método de extensión a su proyecto. Tenga en cuenta que sus objetos listados deben implementar IEquatable!

Uso:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Cualquiera sea la Componentclase, los métodos que se muestran aquí paraCar deben implementarse casi de manera idéntica.

Es muy importante tener en cuenta cómo hemos escrito GetHashCode. Para implementar correctamente IEquatable, Equalsy GetHashCode debe operar en las propiedades de la instancia de una manera lógicamente compatible.

Dos listas con el mismo contenido siguen siendo objetos diferentes y producirán diferentes códigos hash. Como queremos que estas dos listas sean tratadas como iguales, debemos dejar que GetHashCodeproduzca el mismo valor para cada una de ellas. Podemos lograr esto delegando el código hash a cada elemento de la lista y usando el XOR bit a bit estándar para combinarlos a todos. XOR es independiente del orden, por lo que no importa si las listas están ordenadas de manera diferente. Solo importa que no contengan nada más que miembros equivalentes.

Nota: el nombre extraño implica el hecho de que el método no considera el orden de los elementos en la lista. Si te importa el orden de los elementos en la lista, ¡este método no es para ti!

Devon Parsons
fuente
1

He usado este código para comparar dos listas que tienen millones de registros.

Este método no tomará mucho tiempo.

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }
Sathish
fuente
0

Si solo se necesita un resultado combinado, esto también funcionará:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

donde T es el tipo de elemento de listas.

Ali Khaleghi Karsalari
fuente
-1

Puede ser divertido, pero funciona para mí.

string.Join ("", List1)! = string.Join ("", List2)

Jibz
fuente
como está escrito aquí, ni siquiera funcionaría para List <string> o List <int>, como por ejemplo las dos listas 11; 2; 3 y 1; 12; 3 serían idénticas ya que no une las cadenas con algunos separador único que no es un elemento posible en la lista. Aparte de eso, concatenar cadenas para una lista con muchos elementos es probablemente un asesino de rendimiento.
SwissCoder
@SwissCoder: Estás equivocado, esto no es un asesino performacne para string. Si tiene dos listas con 50.000 cadenas (cada una de longitud 3), este algoritmo necesita 3 ms en mi máquina. La respuesta aceptada necesita 7. Creo que el truco es que Jibz solo necesita una comparación de cadena. Por supuesto que tiene que agregar un separador único.
usuario1027167
@ user1027167: No estoy hablando de comparar cadenas directamente (ya que esta tampoco es la pregunta). Llamar al método .ToString () de todos los objetos en una Lista con 50,000 objetos puede crear una cadena enorme, dependiendo de cómo se implemente. No creo que ese sea el camino a seguir. Entonces también es arriesgado confiar en que un carácter o cadena sea "único", el código no sería realmente reutilizable de esa manera.
SwissCoder
Ok eso es verdad. El interlocutor solicitó la forma más rápida sin dar el tipo de datos de sus listas. Probablemente esta respuesta es la forma más rápida para el caso de uso del interlocutor.
user1027167
-3

Creo que esta es una manera simple y fácil de comparar dos listas elemento por elemento

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)
usuario10915707
fuente
3
Esta es una pregunta de C # y no ha proporcionado el código de C #.
Wai Ha Lee
1
Quizás podría eliminar esta respuesta y moverla a (por ejemplo) ¿Cómo puedo comparar dos listas en Python y devolver coincidencias ?
Wai Ha Lee
-4

Esta es la mejor solución que encontrarás

var list3 = list1.Where(l => list2.ToList().Contains(l));
Fajoui El Mahdi
fuente
1
Esto es realmente muy malo porque crea un nuevo List<T>para cada elemento en list1. También se llama list3al resultado cuando no es un List<T>.
Wai Ha Lee