2010-05-26 51 views
19

我有兩個系列ab。我想要計算ab中的一組項目,但不能同時使用這兩個項目(邏輯上的排他或)。隨着LINQ,我能想出這樣的:LINQ和設置差異

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

我不知道是否有產生兩個集合之間的差異的其他更有效或更緊湊的方式。

編輯1:Jon Skeet發佈了第一個解決方案,它不依靠HashSet來保存物品的順序。我想知道是否有其他方法可以在輸出中保留ab的順序。

+0

如果a或b包含重複項,該怎麼辦? – 2010-05-26 06:00:32

+0

就我而言,'a'和'b'不包含重複項,所以這不是我關心的問題。 – 2010-05-26 06:17:31

回答

24

使用HashSet<T>直接 - 它有一個SymmetricExceptWith方法:

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

編輯:如果你想維持秩序,這裏是一個另類:

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

這有以下一些重要區別:

  • Both a and b被迭代兩次。在某些情況下,這可能是一件非常糟糕的事情 - 您可以撥打ToList開始保留緩衝區。
  • 如果在ab中有重複,它們將被多次產生。如果你想避免這種情況,你可以保留一組已經產生的值。在這一點上,這將是等同於:

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

這仍然只是組操作,而不是在原來的代碼三個雖然。

+0

感謝Jon的快速回復。只要您對這些項目的原始順序不感興趣,HashSet就可以正常工作。如果我想保持'a'和'b'中項目的順序不同? – 2010-05-26 05:48:21

+0

@Pierre:我用另外幾個選項編輯了我的答案。 – 2010-05-26 06:05:12

+0

非常感謝您的時間。在我的情況下,'a'和'b'不包含重複項,所以這不是一個問題。你提出的LINQ表達式比涉及'HashSet'的代碼更可讀(因此可維護)。我喜歡! – 2010-05-26 06:16:07

3

鑑於a.Except(b)和b.Except(a)不相交,您可以使用concat而不是union,保存設置的運算符(並且concat效率更高)。

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

這仍然貫穿每個列表兩次。

+0

謝謝;你是對的,因爲我的輸入是不相交的,Concat將比'Union'更快;我忽略了這一點。 – 2010-05-26 12:57:49

0

我們必須在我公司的一個項目類似的需求,所以我們寫了這個擴展:

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

它比較兩個集合的元素(使用IEqualityComparer與否,在你的選擇)。

  • 返回的對象,一個EnumerablePair<T>,包含正在leftOperandrightOperand物體,但不能同時(XOR)。
  • EnumerablePair<T>.Left包含leftOperand中的對象,但不包含在rightOperand中的對象。
  • EnumerablePair<T>.Right包含rightOperand中的對象,但不包含在leftOperand中。

您可以使用擴展這樣的:

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

xorListleftXorrightXorIEnumerable<T>