2010-06-04 10 views
8

Acabo de descubrir que Except() eliminará todos los elementos en la segunda lista de la primera, pero también tiene el efecto de que hace que todos los elementos en el resultado devuelto sean distintos.Excepto que tiene un efecto similar a Distinct?

manera simple alrededor que estoy usando es Where(v => !secondList.Contains(v))

Puede alguien me explique por qué este es el comportamiento, y me apuntan a la documentación que explica esto si es posible?

Respuesta

18

La documentación de los estados de función Except:

la diferencia de conjuntos de dos secuencias utilizando el comparador de igualdad predeterminado para comparar los valores.

La diferencia establecida de dos conjuntos se define como los miembros del primer conjunto que no aparecen en el segundo conjunto.

La palabra importante aquí es conjunto, que es defined como:

... una estructura de datos abstracta que puede almacenar ciertos valores, sin ningún orden en particular, y no hay valores repetidos ..

Dado que Except se documenta como una operación basada en conjuntos, también tiene el efecto de hacer que los valores resultantes sean distintos.

+0

Bueno, eso tiene un poco más de sentido. Ha sido mucho tiempo desde que tomé las matemáticas establecidas. Gracias. – Stephan

+2

@Stephan - Aunque, tengo que decir que el comportamiento documentado me parece un poco extraño dado que 'IEnumerable ' es una secuencia y no un conjunto, por lo que tener un método de extensión de propósito general para una secuencia ha establecido una semántica es algo extraño Pero, de nuevo, supongo que la alternativa de tener un método 'Except' de' IEnumerable 'tiene una semántica de secuencia y una de' ISet 'ha establecido que la semántica es aún peor, ya que' ISet 'hereda' IEnumerable 'y así la semántica diferiría dependiendo de cómo el método de extensión estaba obligado por el compilador. –

+1

@Greg, Mono parece obtener esto [mal] (http://anonsvn.mono-project.com/viewvc/trunk/mcs/class/System.Core/System.Linq/Enumerable.cs#l794) (esto es en realidad el segundo error de Mono Enumerable que he encontrado a través de StackOverflow). Devuelve elementos del primero tantas veces como aparezcan. Mi pregunta es, si se trata de una diferencia establecida, ¿por qué la documentación muestra Enumerable. Excepto preservar el orden de los primeros elementos (también dice que devuelve "A sequence")? Los conjuntos no conservan el orden. ¿Es Enumerable.Except garantizado? –

0

Usted escribió:

manera simple en torno estoy usando es Where(v => !secondList.Contains(v))

Al hacer esto, todavía hay Distict hecho con secondList.

Por ejemplo:

var firstStrings = new [] { "1", null, null, null, "3", "3" }; 
var secondStrings = new [] { "1", "1", "1", null, null, "4" }; 
var resultStrings = firstStrings.Where(v => !secondStrings.Contains(v)); // 3, 3 

creé un método de extensión no tener distinta en absoluto. Examle de uso:

var result2Strings = firstStrings.ExceptAll(secondStrings).ToList(); // null, 3, 3 

Esto es lo que hace:

enter image description here

Ésta es la fuente:

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second) 
{ 
    // Do not call reuse the overload method because that is a slower imlementation 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 

    var secondList = second.ToList(); 
    return first.Where(s => !secondList.Remove(s)); 
} 

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, 
    IEqualityComparer<TSource> comparer) 
{ 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 
    var comparerUsed = comparer ?? EqualityComparer<TSource>.Default; 

    var secondList = second.ToList(); 
    foreach (var item in first) 
    { 
     if (secondList.Contains(item, comparerUsed)) 
     { 
      secondList.Remove(item); 
     } 
     else 
     { 
      yield return item; 
     } 
    } 
} 

Editar: Una Implementación más rápida, basado en la observación de DigEmAll

public static IEnumerable<TSource> ExceptAll<TSource>(
     this IEnumerable<TSource> first, 
     IEnumerable<TSource> second) 
{ 
    return ExceptAll(first, second, null); 
} 

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, 
    IEqualityComparer<TSource> comparer) 
{ 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 


    var secondCounts = new Dictionary<TSource, int>(comparer ?? EqualityComparer<TSource>.Default); 
    int count; 
    int nullCount = 0; 

    // Count the values from second 
    foreach (var item in second) 
    { 
     if (item == null) 
     { 
      nullCount++; 
     } 
     else 
     { 
      if (secondCounts.TryGetValue(item, out count)) 
      { 
       secondCounts[item] = count + 1; 
      } 
      else 
      { 
       secondCounts.Add(item, 1); 
      } 
     } 
    } 

    // Yield the values from first 
    foreach (var item in first) 
    { 
     if (item == null) 
     { 
      nullCount--; 
      if (nullCount < 0) 
      { 
       yield return item; 
      } 
     } 
     else 
     { 
      if (secondCounts.TryGetValue(item, out count)) 
      { 
       if (count == 0) 
       { 
        secondCounts.Remove(item); 
        yield return item; 
       } 
       else 
       { 
        secondCounts[item] = count - 1; 
       } 
      } 
      else 
      { 
       yield return item; 
      } 
     } 
    } 
} 

More info en mi blog (también variante para Intersecar y Unión)

+3

Esta es una opción terriblemente ineficiente ya que buscar en una lista para cada artículo es bastante caro, como lo es eliminar elementos de una lista. El idioma 'Distinct' utiliza un HashSet, y como resultado es * mucho * más eficiente. Puede modificar fácilmente su implementación para generar elementos repetidos en la fuente sin matar su eficiencia. – Servy

+0

Estoy de acuerdo con @Servy, tanto 'Contains' como' Remove' son O (_n_) operaciones y las estás haciendo en un bucle. – Magnus

+0

@Servy, sé que es MUCHO más lento, pero el resultado es diferente. Su crítica tiene sentido si tiene una implementación que es más rápida y hace lo mismo que se muestra en la imagen. Tengo curiosidad por una mejor solución. ¿Cómo se modifica su implementación para producir elementos repetidos en la fuente sin matar su eficiencia? –

Cuestiones relacionadas