我有两个集合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
的顺序。
答案 0 :(得分:26)
直接使用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))
但仍然只有两个设置操作,而不是原始代码中的三个。
答案 1 :(得分:5)
给定a。除了(b)和b。除了(a)是不相交的,你可以使用concat
代替union
,保存一个集合运算符(而concat
更有效率)。
return a.Except (b).Concat (b.Except (a));
这仍然会在每个列表中运行两次。
答案 2 :(得分: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>
包含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>
。