代码之家  ›  专栏  ›  技术社区  ›  Pierre Arnaud

直线和集差

  •  19
  • Pierre Arnaud  · 技术社区  · 14 年前

    我有两件收藏品 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 . 我想知道是否还有其他方法可以保持 在输出中。

    3 回复  |  直到 7 年前
        1
  •  26
  •   Jon Skeet    14 年前

    使用 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.Concat(b).Except(a.Intersect(b))
      

    那还只是 但是,在原始代码中设置操作而不是三个。

        2
  •  5
  •   Cameron MacFarland    14 年前

    如果a.except(b)和b.except(a)不相交,可以使用 concat 而不是 union ,保存一个集合运算符(和 康塔特 更有效)。

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

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

        3
  •  0
  •   Xav987    7 年前

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

    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 包含中的对象 左操作数 但不在 右操作数 .
    • EnumerablePair<T>.Right 包含中的对象 右操作数 但不在 左操作数 .

    您可以这样使用扩展名:

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

    xorList , leftXor rightXor IEnumerable<T> .