21

我有两个系列ab. 我想计算其中一个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. 我想知道是否有其他方法可以保留输出的顺序ab输出。

4

3 回答 3

27

直接使用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;
    }
}

这有以下重要区别:

  • 两者ab都被迭代了两次。在某些情况下,这可能是一件非常糟糕的事情——你可以调用ToList它们中的每一个来开始保留一个缓冲区。
  • a如果或中有重复项b,它们将被多次生成。如果你想避免这种情况,你可以保留一组已经产生的值。在这一点上,它相当于:

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

不过,这仍然只是两个集合操作,而不是原始代码中的三个。

于 2010-05-26T05:39:13.217 回答
6

鉴于 a.Except(b) 和 b.Except(a) 是不相交的,您可以使用concat而不是union,节省集合运算符(并且concat更有效)。

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

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

于 2010-05-26T06:26:48.207 回答
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或不使用,由您选择)。

  • 返回的对象 anEnumerablePair<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;

xorListleftXor并且rightXorIEnumerable<T>

于 2017-08-03T09:33:18.387 回答