LINQ并设置差异
我有两个集合a
和b
。 我想计算a
或b
的项集,但不能同时计算(逻辑异或)。 使用LINQ,我可以想出这个:
IEnumerable Delta(IEnumerable a, IEnumerable b) { return a.Except (b).Union (b.Except (a)); }
我想知道是否还有其他更有效或更紧凑的方法来产生两个集合之间的差异。
编辑1:Jon Skeet发布了第一个解决方案,它不依赖于HashSet
保留项目的顺序。 我想知道是否还有其他方法可以保留输出中a
和b
的顺序。
直接使用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; } }
这有以下重要区别:
-
a
和b
都迭代两次。 在某些情况下,这可能是一件非常糟糕的事情 – 你可以在每个上面调用ToList
来保留缓冲区。 -
如果
a
或b
有重复项,它们将被多次出现。 如果你想避免这种情况,你可以保留一组已经产生的值。 在这一点上,它将等同于: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
包含leftOperand
或rightOperand
中的对象,但不包含两者(XOR)。 -
EnumerablePair
包含.Left leftOperand
但不在rightOperand
中的rightOperand
。 -
EnumerablePair
包含.Right rightOperand
但不在leftOperand
中的leftOperand
。
您可以像这样使用扩展名:
var xorList = list1.ExclusiveDisjunction(list2); var leftXor = xorList.Left; var rightXor = xorList.Right;
xorList
, leftXor
和rightXor
是IEnumerable
。