2010-05-26 58 views
19

我有两个系列ab。我想要计算ab中的一组项目,但不能同时使用这两个项目(逻辑上的排他或)。随着LINQ,我能想出这样的:LINQ和设置差异

IEnumerable<T> Delta<T>(IEnumerable<T> a, IEnumerable<T> b) 
{ 
    return a.Except (b).Union (b.Except (a)); 
} 

我不知道是否有产生两个集合之间的差异的其他更有效或更紧凑的方式。

编辑1:Jon Skeet发布了第一个解决方案,它不依靠HashSet来保存物品的顺序。我想知道是否有其他方法可以在输出中保留ab的顺序。

+0

如果a或b包含重复项,该怎么办? – 2010-05-26 06:00:32

+0

就我而言,'a'和'b'不包含重复项,所以这不是我关心的问题。 – 2010-05-26 06:17:31

回答

24

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

这有以下一些重要区别:

  • Both a and b被迭代两次。在某些情况下,这可能是一件非常糟糕的事情 - 您可以拨打ToList开始保留缓冲区。
  • 如果在ab中有重复,它们将被多次产生。如果你想避免这种情况,你可以保留一组已经产生的值。在这一点上,这将是等同于:

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

这仍然只是组操作,而不是在原来的代码三个虽然。

+0

感谢Jon的快速回复。只要您对这些项目的原始顺序不感兴趣,HashSet就可以正常工作。如果我想保持'a'和'b'中项目的顺序不同? – 2010-05-26 05:48:21

+0

@Pierre:我用另外几个选项编辑了我的答案。 – 2010-05-26 06:05:12

+0

非常感谢您的时间。在我的情况下,'a'和'b'不包含重复项,所以这不是一个问题。你提出的LINQ表达式比涉及'HashSet'的代码更可读(因此可维护)。我喜欢! – 2010-05-26 06:16:07

3

鉴于a.Except(b)和b.Except(a)不相交,您可以使用concat而不是union,保存设置的运算符(并且concat效率更高)。

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

这仍然贯穿每个列表两次。

+0

谢谢;你是对的,因为我的输入是不相交的,Concat将比'Union'更快;我忽略了这一点。 – 2010-05-26 12:57:49

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>,包含正在leftOperandrightOperand物体,但不能同时(XOR)。
  • EnumerablePair<T>.Left包含leftOperand中的对象,但不包含在rightOperand中的对象。
  • EnumerablePair<T>.Right包含rightOperand中的对象,但不包含在leftOperand中。

您可以使用扩展这样的:

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

xorListleftXorrightXorIEnumerable<T>