2010-05-26 10 views
19

Ich habe zwei Sammlungen a und b. Ich möchte die Menge der Elemente entweder in a oder b berechnen, aber nicht in beiden (eine logische exklusive oder). Mit LINQ, kann ich mit diesen kommen:LINQ und setzen Unterschied

IEnumerable<T> Delta<T>(IEnumerable<T> a, IEnumerable<T> b) 
{ 
    return a.Except (b).Union (b.Except (a)); 
} 

Ich frage mich, ob es noch andere effiziente oder kompaktere Möglichkeiten, den Unterschied zwischen den beiden Sammlungen zu erzeugen.

Bearbeiten 1: Jon Skeet postulierte eine erste Lösung, die nicht die Reihenfolge der Elemente durch eine HashSet verlässt. Ich frage mich, ob es andere Ansätze gibt, die die Reihenfolge a und b in der Ausgabe beibehalten würden.

+0

Was passiert, wenn a oder b Duplikate enthalten? –

+0

In meinem Fall enthalten 'a' und' b' keine Duplikate, dies ist für mich kein Problem. –

Antwort

24

Verwendung HashSet<T> direkt - es hat eine SymmetricExceptWith Methode:

HashSet<T> data = new HashSet<T>(a); 
data.SymmetricExceptWith(b); 

EDIT: Wenn Sie den Auftrag erhalten wollen, ist hier eine Alternative:

HashSet<T> data = new HashSet<T>(a); 
data.IntersectWith(b); 
foreach (T t in a.Concat(b)) 
{ 
    if (!data.Contains(t)) 
    { 
     yield return t; 
    } 
} 

Dies hat folgende wichtige Unterschiede:

  • Sowohl a als auch b werden zweimal durchlaufen. In einigen Fällen könnte das eine sehr schlechte Sache sein - Sie könnten ToList auf jedem von ihnen aufrufen, um mit einem Puffer zu behalten.
  • Wenn in a oder b Duplikate vorhanden sind, werden sie mehrmals ausgegeben. Wenn Sie dies vermeiden möchten, können Sie eine Reihe bereits ermittelter Werte beibehalten. An dieser Stelle wäre es äquivalent zu:

    a.Concat(b).Except(a.Intersect(b)) 
    

Das noch zwei Set-Operationen nur ist anstelle der drei in Ihrer ursprünglichen Code though.

+0

Danke Jon für deine schnelle Antwort. HashSet funktioniert gut, solange Sie nicht an der ursprünglichen Reihenfolge der Elemente interessiert sind. Was ist, wenn ich die Reihenfolge der Elemente in "a" und "b" in der Differenz halten möchte? –

+0

@Pierre: Ich habe meine Antwort mit ein paar anderen Optionen bearbeitet. –

+0

Vielen Dank für Ihre Zeit. In meinem Fall enthalten "a" und "b" keine Duplikate, dies ist also kein Problem. Der LINQ-Ausdruck, den Sie vorschlagen, ist wesentlich lesbarer (und daher wartbarer) als der Code, der das HashSet enthält. Ich mag das! –

3

Da a.Except (b) und b.Except (a) disjunkt sind, können Sie concat statt union, verwenden Sie einen Satz Bediener speichern (und concat ist effizienter).

return a.Except (b).Concat (b.Except (a)); 

Dies läuft noch zweimal durch jede Liste.

+0

Danke; Sie haben Recht, 'Concat' wird schneller als 'Union' sein, da meine Eingaben nicht zusammenhängen; Ich hatte diesen Punkt übersehen. –

0

Wir hatten ein ähnliches Bedürfnis für ein Projekt in meiner Firma, so schrieben wir diese Erweiterung:

public class EnumerablePair<T> : IReadOnlyCollection<T> 
{ 
    private IReadOnlyCollection<T> _Left; 
    private IReadOnlyCollection<T> _Right; 
    private IEnumerable<T> _Union; 
    private int _Count; 
    public EnumerablePair(IEnumerable<T> left, IEnumerable<T> right) 
    { 
     _Left = left?.ToList() ?? Enumerable.Empty<T>().ToList(); 
     _Right = right?.ToList() ?? Enumerable.Empty<T>().ToList(); 
     _Count = Left.Count + Right.Count; 
     _Union = Left.Union(Right); 
    } 

    public int Count => _Count; 
    public IReadOnlyCollection<T> Left { get => _Left; } 
    public IReadOnlyCollection<T> Right { get => _Right; } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return _Union.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return _Union.GetEnumerator(); 
    } 
} 

public static class EnumerableExtension 
{ 
    public static EnumerablePair<T> ExclusiveDisjunction<T>(this IEnumerable<T> leftOperand, IEnumerable<T> rightOperand, IEqualityComparer<T> comparer = null) 
    { 
     if (leftOperand == null) 
      throw new ArgumentNullException(nameof(leftOperand), $"{nameof(leftOperand)} is null."); 
     if (rightOperand == null) 
      throw new ArgumentNullException(nameof(rightOperand), $"{nameof(rightOperand)} is null."); 

     // TODO : Can be optimized if one of the IEnumerable parameters is empty. 

     bool leftIsBigger = leftOperand.Count() > rightOperand.Count(); 
     var biggestOperand = leftIsBigger ? leftOperand.ToList() : rightOperand.ToList(); 
     var smallestOperand = leftIsBigger ? rightOperand.ToList() : leftOperand.ToList(); 

     var except1 = biggestOperand.ToList(); 
     var except2 = Enumerable.Empty<T>().ToList(); 

     Func<T, T, bool> areEquals; 
     if (comparer != null) 
      areEquals = (one, theOther) => comparer.Equals(one, theOther); 
     else 
      areEquals = (one, theOther) => one?.Equals(theOther) ?? theOther == null; 

     foreach (T t in smallestOperand) 
      if (except1.RemoveAll(item => areEquals(item, t)) == 0) 
       except2.Add(t); 

     if (leftIsBigger) 
      return new EnumerablePair<T>(except1, except2); 
     return new EnumerablePair<T>(except2, except1); 
    } 
} 

Es Elemente aus zwei Sammlungen vergleicht (mit einem IEqualityComparer oder nicht, bei Ihrer Wahl).

  • Das zurückgegebene Objekt ein EnumerablePair<T>, enthält Objekte, die in leftOperand oder rightOperand sind, aber nicht beide (XOR).
  • EnumerablePair<T>.Left enthält Objekte, die in leftOperand sind, aber nicht in rightOperand.
  • EnumerablePair<T>.Right enthält Objekte, die in rightOperand, aber nicht in leftOperand sind.

Sie können die Erweiterung wie folgt verwenden:

var xorList = list1.ExclusiveDisjunction(list2); 
var leftXor = xorList.Left; 
var rightXor = xorList.Right; 

xorList, leftXor und rightXor sind IEnumerable<T>.