LINQ并设置差异

我有两个集合ab 。 我想计算ab的项集,但不能同时计算(逻辑异或)。 使用LINQ,我可以想出这个:

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

我想知道是否还有其他更有效或更紧凑的方法来产生两个集合之间的差异。

编辑1:Jon Skeet发布了第一个解决方案,它不依赖于HashSet保留项目的顺序。 我想知道是否还有其他方法可以保留输出中ab的顺序。

直接使用HashSet – 它有一个SymmetricExceptWith方法:

 HashSet data = new HashSet(a); data.SymmetricExceptWith(b); 

编辑:如果你想维持订单,这里有一个替代方案:

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

这有以下重要区别:

  • ab都迭代两次。 在某些情况下,这可能是一件非常糟糕的事情 – 你可以在每个上面调用ToList来保留缓冲区。
  • 如果ab有重复项,它们将被多次出现。 如果你想避免这种情况,你可以保留一组已经产生的值。 在这一点上,它将等同于:

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

尽管如此,这仍然只是两个设置操作而不是原始代码中的三个操作。

给定a.Except(b)和b.Except(a)是不相交的,你可以使用concat代替union ,保存set运算符( concat更有效)。

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

这仍然会在每个列表中运行两次。

我们对公司的项目有类似的需求,所以我们写了这个扩展:

 public class EnumerablePair : IReadOnlyCollection { private IReadOnlyCollection _Left; private IReadOnlyCollection _Right; private IEnumerable _Union; private int _Count; public EnumerablePair(IEnumerable left, IEnumerable right) { _Left = left?.ToList() ?? Enumerable.Empty().ToList(); _Right = right?.ToList() ?? Enumerable.Empty().ToList(); _Count = Left.Count + Right.Count; _Union = Left.Union(Right); } public int Count => _Count; public IReadOnlyCollection Left { get => _Left; } public IReadOnlyCollection Right { get => _Right; } public IEnumerator GetEnumerator() { return _Union.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _Union.GetEnumerator(); } } public static class EnumerableExtension { public static EnumerablePair ExclusiveDisjunction(this IEnumerable leftOperand, IEnumerable rightOperand, IEqualityComparer 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().ToList(); Func 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(except1, except2); return new EnumerablePair(except2, except1); } } 

它比较两个集合的元素(使用IEqualityComparer与否,根据您的选择)。

  • 返回的对象EnumerablePair包含leftOperandrightOperand中的对象,但不包含两者(XOR)。
  • EnumerablePair.Left包含leftOperand但不在rightOperand中的rightOperand
  • EnumerablePair.Right包含rightOperand但不在leftOperand中的leftOperand

您可以像这样使用扩展名:

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

xorListleftXorrightXorIEnumerable