2010-06-04 12 views
8

Ho appena scoperto che Except() rimuoverà tutti gli elementi nella seconda lista dal primo, ma ha anche l'effetto che rende distinti tutti gli elementi nel risultato restituito.Tranne che ha un effetto simile a Distinct?

modo semplice giro che sto usando è Where(v => !secondList.Contains(v))

Qualcuno può spiegare a me perché questo è il comportamento, e, se possibile, mi ha punto la documentazione che spiega questo?

risposta

18

La documentazione per gli stati Except funzione:

Produce la differenza di set di due sequenze utilizzando l'operatore di confronto uguaglianze predefinito per confrontare i valori.

La differenza di set di due set è definita come i membri del primo set che non compaiono nel secondo set.

La parola importante qui è set, che è defined come:

... una struttura dati astratta che può memorizzare certi valori, senza alcun ordine particolare, e senza valori ripetuti ..

Poiché Except è documentato come un'operazione basata su set, ha anche l'effetto di rendere distinti i valori risultanti.

+0

Beh, questo ha un po 'più senso. È passato molto tempo da quando ho preso la matematica. Grazie. – Stephan

+2

@Stephan - Anche se, devo dire, il comportamento documentato mi sembra un po 'strano dato che 'IEnumerable ' è una sequenza e non un set, quindi avere un metodo di estensione generico per una sequenza ha impostato la semantica è un po 'strano Ma di nuovo, suppongo che l'alternativa di avere un metodo 'Except' da' IEnumerable 'abbia semantica di sequenza e uno da' ISet 'la semantica impostata sia ancora peggiore, poiché' ISet 'eredita' IEnumerable 'e quindi la semantica sarebbe diversa a seconda di come il metodo di estensione era vincolato dal compilatore. –

+1

@Greg, Mono sembra ottenere questo [sbagliato] (http://anonsvn.mono-project.com/viewvc/trunk/mcs/class/System.Core/System.Linq/Enumerable.cs#l794) (questo è in realtà il secondo bug Mono Enumerable che ho trovato attraverso StackOverflow). Restituisce gli elementi dal primo tutte le volte che appaiono. La mia domanda è, se è una differenza di set, perché la documentazione mostra Enumerable.Except preservando l'ordine dei primi elementi (dice anche che restituisce "Una sequenza")? Gli insiemi non conservano l'ordine. Enumerable.Except garantito a? –

0

Hai scritto:

modo semplice giro che sto usando è Where(v => !secondList.Contains(v))

Quando si esegue questa operazione, c'è ancora Distict fatto con secondList.

Ad esempio:

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 

ho creato un metodo di estensione non avere distinto affatto. Examle di utilizzo:

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

Questo è ciò che fa:

enter image description here

Questa è la fonte:

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; 
     } 
    } 
} 

Edit: A Implementazione più veloce, basato sul commento di 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 sul mio blog (anche la variante per Intersect e Union)

+3

Questa è un'opzione orribilmente inefficiente poiché la ricerca in un elenco per ogni articolo è piuttosto costosa, come lo è la rimozione di elementi da un elenco. Il linguaggio 'Distinct' usa un HashSet, e come risultato * molto * più efficiente. È possibile modificare facilmente la sua implementazione per fornire elementi ripetuti nella fonte senza uccidere la sua efficienza. – Servy

+0

Sono d'accordo con @Servy, sia 'Contains' che' Remove' sono operazioni O (_n_) e le stai facendo in un loop. – Magnus

+0

@Servy, so che è MOLTO più lento, ma il risultato è diverso. Il tuo critico ha senso se hai un'implementazione che è più veloce e fa la stessa cosa mostrata nell'immagine. Sono curioso di una soluzione migliore. Come modificare la sua implementazione per produrre elementi ripetuti nella fonte senza uccidere la sua efficienza? –

Problemi correlati