Finding symmetric difference with LINQ

后端 未结 3 1066
心在旅途
心在旅途 2020-12-01 14:13

I have two collections a and b. I would like to compute the set of items in either a or b, but not in both (a logical exc

3条回答
  •  情深已故
    2020-12-01 14:49

    We had a similar need for a project in my company, so we wrote this extension:

    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);
        }
    }
    

    It compares elements of two collections (using an IEqualityComparer or not, at your choice).

    • The returned object, an EnumerablePair, contains objects that are in leftOperand or rightOperand, but not both (XOR).
    • EnumerablePair.Left contains objects that are in leftOperand but not in rightOperand.
    • EnumerablePair.Right contains objects that are in rightOperand but not in leftOperand.

    You can use the extension like this :

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

    xorList, leftXor and rightXor are IEnumerable.

提交回复
热议问题