2011-07-13 27 views
6

tengo dos coleccionesC# forma más eficiente de la comparación de dos colecciones

List<Car> currentCars = GetCurrentCars(); 
List<Car> newCars = GetNewCars(); 

que no quiero utilizar bucle foreach o algo porque creo que debería ser mucho mejor forma de hacer esto.

Busco manera más eficiente para comparar este colecciones y para obtener resultados:

  1. Lista de los coches que están en newcars ​​y no en currentCars
  2. Lista de coches que no están en newcars ​​y en currentCars

Type Car tiene la propiedad int Id.

había una respuesta, que ya ha sido borrada diciendo Lo que quiero decir diciendo eficiente: menos código, menos la mecánica, y los casos más legibles

Así que pensar de esta manera lo que es los casos que he?

¿Qué sería menos código, menos mecánica y más casos legibles?

+1

Las operaciones que describe son "diferencias establecidas". Si necesita calcular las diferencias de configuración mucho o en conjuntos grandes, no debe usar List en primer lugar. Debería utilizar HashSet , una estructura de datos específicamente optimizada para calcular este tipo de diferencias. –

+0

@Eric Lippert en mi caso son juegos muy pequeños (1-3 artículos), así que creo que no es necesario en HashSet . – Joper

Respuesta

10

Puede utilizar Except:

var currentCarsNotInNewCars = currentCars.Except(newCars); 
var newCarsNotInCurrentCars = newCars.Except(currentCars); 

Pero esto no tiene ninguna ventaja de rendimiento sobre la solución foreach. Simplemente se ve más limpio.
Además, tenga en cuenta que debe implementar IEquatable<T> para su clase Car, por lo que la comparación se realiza en la ID y no en la referencia.

Performancewise, un mejor enfoque sería no utilizar un List<T> sino un Dictionary<TKey, TValue> con el identificador como la clave:

var currentCarsDictionary = currentCars.ToDictionary(x => x.ID); 
var newCarsDictionary = newCars.ToDictionary(x => x.ID); 

var currentCarsNotInNewCars = 
    currentCarsDictionary.Where(x => !newCarsDictionary.ContainsKey(x.Key)) 
         .Select(x => x.Value); 

var newCarsNotInCurrentCars = 
    newCarsDictionary.Where(x => !currentCarsDictionary.ContainsKey(x.Key)) 
        .Select(x => x.Value); 
+1

Pero esto es en esencia un ciclo – landoncz

+0

@landoncz: Lo sé. Creo que es imposible no usar un bucle. –

+0

Estoy de acuerdo, técnicamente tienes que usar un bucle a menos que vayas a hacer una recursión loca (entonces vas a tener algo que no se puede mantener y también corres el riesgo de volar la pila). –

12

Usted puede hacerlo de esta manera:

// 1) List of cars in newCars and not in currentCars 
var newButNotCurrentCars = newCars.Except(currentCars); 

// 2) List of cars in currentCars and not in newCars 
var currentButNotNewCars = currentCars.Except(newCars); 

Los usos de código el método de extensión Enumerable.Except (disponible en .Net 3.5 y más).

Creo que esto cumple sus criterios de "menos código, menos mecánica y más legible".

+0

+1, LINQ es probablemente una manera fácil, especialmente con [Excepto()] (http://msdn.microsoft.com/en-us/library/system.linq.enumerable.except.aspx). –

+1

+1, pero no hay ninguna garantía de que, excepto que supere un rendimiento para cada uno, ya que lo más probable es que use Ienumerable y foreach de cualquier manera. – rerun

+0

LINQ = loop, que dijo que no quería ... – landoncz

2

Anularía el Equals de Car para comparar por id y luego podría usar el método de extensión IEnumerable.Except. Si no puede anular el Equals, puede crear su propio IEqualityComparer<Car> que compare dos autos por identificación.

class CarComparer : IEqualityComparer<Car> 
{ 
    public bool Equals(Car x, Car y) 
    { 
     return x != null && y != null && x.Id == y.Id; 
    } 

    public int GetHashCode(Car obj) 
    { 
     return obj == null ? 0 : obj.Id; 
    } 
} 
+0

Creo que esta es la única forma de hacerlo sin pasar por cada elemento de alguna manera. – landoncz

+0

@landoncz: No veo cómo esto evita un ciclo. También sugiere utilizar el método de extensión 'Except', aunque lo escribió mal. –

+0

Lo siento, estaba pensando que estaba anulando toda la lista como esta, D'oh, supongo que no se puede agregar el código a las respuestas ... – landoncz

2

Puede utilizar LINQ ...

 List<Car> currentCars = new List<Car>(); 
     List<Car> newCars = new List<Car>(); 

     List<Car> currentButNotNew = currentCars.Where(c => !newCars.Contains(c)).ToList(); 
     List<Car> newButNotCurrent = newCars.Where(c => !currentCars.Contains(c)).ToList(); 

... pero no se deje engañar.Puede ser menos código para usted, pero definitivamente habrá algunos bucles en alguna parte

EDIT: No nos dimos cuenta que había un método Excepto :(

3

Si se inicia con ellos en HashSet s que pueda utilizar el método Except.

HashSet<Car> currentCars = GetCurrentCars(); 
HashSet<Car> newCars = GetNewCars(); 

currentCars.Except(newCars); 
newCars.Except(currentCars); 

sería mucho más rápido w/un conjunto de una lista. (Bajo el capó una lista sólo está haciendo un foreach, conjuntos pueden ser optimizados).

+1

+1 para HashSet. Aunque funcionará más rápido, consumirá más memoria y el costo inicial de poblar el HashSet será mayor. Solo debe usar HashSet si planea usar la misma colección muchas veces. –

+0

Muy cierto. Sin embargo, la compensación es casi siempre del tamaño de la velocidad. – Crisfole

1

Si' buscando eficiencia, implemente IComparable en Cars (s) describiendo su identificación única) y use una SortedList. Luego puede recorrer sus colecciones y evaluar sus cheques en O (n). Esto, por supuesto, tiene un costo adicional para las inserciones de la Lista para mantener la naturaleza ordenada.

0

Puede copiar la lista más pequeña en una colección basada en una tabla hash como HashSet o Dictionary y luego iterar sobre la segunda lista y verificar si el elemento existe en la tabla hash.

esto reducirá el tiempo de O (N^2) en el caso forense ingenuo dentro de foreach a O (N).

Esto es lo mejor que puede hacer sin saber más acerca de las listas (es posible que pueda hacer un pequeño mejor si las listas están ordenadas, por ejemplo, pero, dado que tiene que "tocar" cada automóvil al menos una vez para verificar si está en la nueva lista de automóviles, nunca podrá hacerlo mejor que O (N))

0

Si una comparación de la propiedad de Id es suficiente para decir si un automóvil es igual a otro, para evitar algunos tipo de bucle, puede anular la Lista con su propia clase que realiza un seguimiento de los elementos y utiliza el IEqualityComparer en toda la colección, como este:

class CarComparer : IList<Car>, IEquatable<CarComparer> 
{ 
    public bool Equals(CarComparer other) 
    { 
     return object.Equals(GetHashCode(),other.GetHashCode()); 
    } 

    public override int GetHashCode() 
    { 
     return _runningHash; 
    } 

    public void Insert(int index, Car item) 
    { 
     // Update _runningHash here 
     throw new NotImplementedException(); 
    } 

    public void RemoveAt(int index) 
    { 
     // Update _runningHash here 
     throw new NotImplementedException(); 
    } 

    // More IList<Car> Overrides .... 
} 

Luego, solo tiene que anular Add, Remove, etc. y cualquier otro método que pueda afectar los elementos de la lista. Luego puede mantener una variable privada que es un hash de algún tipo de ID de los elementos en la lista. Al anular sus métodos Equals, puede simplemente comparar esta variable privada. No es el enfoque más limpio por el momento (ya que tiene que mantenerse al día con su variable hash), pero dará como resultado que no tenga que pasar para hacer una comparación. Si fuera yo, simplemente usaría Linq como algunos mencionaron aquí ...

Cuestiones relacionadas