查找与Linq的对称差异
-
04-10-2019 - |
题
我有两个收藏 a
和 b
. 。我想在任何一个中计算一组项目 a
或者 b
, ,但不在两者中(逻辑独家或)。有了Linq,我可以提出这一点:
IEnumerable<T> Delta<T>(IEnumerable<T> a, IEnumerable<T> b)
{
return a.Except (b).Union (b.Except (a));
}
我想知道是否还有其他更有效的方法或更紧凑的方式来产生这两个集合之间的差异。
编辑1:乔恩·斯基特(Jon Skeet)发布了第一个解决方案,该解决方案无法通过依靠一个 HashSet
. 。我想知道是否还有其他方法可以保留 a
和 b
在输出中。
解决方案
采用 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;
}
}
这具有以下重要差异:
- 两个都
a
和b
迭代两次。在某些情况下,这可能是一件非常糟糕的事情 - 您可以打电话ToList
他们每个人都从保留缓冲区开始。 如果有重复的
a
或者b
, ,它们将被多次产生。如果您想避免这种情况,则可以保留一组已经收到的值。在这一点上,它等同于:a.Concat(b).Except(a.Intersect(b))
仍然只是 二 设置操作而不是原始代码中的三个。
其他提示
给定的A.Except(b)和B.Except(a)是脱节的,您可以使用 concat
代替 union
, ,保存设置操作员(和 concat
更有效)。
return a.Except (b).Concat (b.Except (a));
这仍然两次通过每个列表。
我们在公司中对项目也有类似的需求,所以我们写了此扩展名:
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>
, ,包含在leftOperand
或者rightOperand
, ,但不是两者(XOR)。 EnumerablePair<T>.Left
包含在leftOperand
但不在rightOperand
.EnumerablePair<T>.Right
包含在rightOperand
但不在leftOperand
.
您可以使用这样的扩展名:
var xorList = list1.ExclusiveDisjunction(list2);
var leftXor = xorList.Left;
var rightXor = xorList.Right;
xorList
, leftXor
和 rightXor
是 IEnumerable<T>
.
不隶属于 StackOverflow