2010-05-26 29 views
19

Tôi có hai bộ sưu tập ab. Tôi muốn tính toán tập hợp các mục trong số a hoặc b, nhưng không phải trong cả hai mục (độc quyền hợp lý hoặc). Với LINQ, tôi có thể đưa ra điều này:LINQ và đặt sự khác biệt

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

Tôi tự hỏi liệu có cách nào khác hiệu quả hơn hoặc nhỏ gọn hơn để tạo sự khác biệt giữa hai bộ sưu tập.

Chỉnh sửa 1: Jon Skeet đăng giải pháp đầu tiên không giữ thứ tự của các mục bằng cách dựa vào số HashSet. Tôi tự hỏi nếu có những cách tiếp cận khác mà sẽ bảo vệ thứ tự của ab trong đầu ra.

+0

Điều gì sẽ xảy ra nếu a hoặc b chứa các bản sao? –

+0

Trong trường hợp của tôi, 'a' và' b' không chứa các bản sao, vì vậy đây không phải là mối quan tâm đối với tôi. –

Trả lời

24

Sử dụng HashSet<T> trực tiếp - nó có một phương pháp SymmetricExceptWith:

HashSet<T> data = new HashSet<T>(a); 
data.SymmetricExceptWith(b); 

EDIT: Nếu bạn muốn duy trì trật tự, đây là một sự thay thế:

HashSet<T> data = new HashSet<T>(a); 
data.IntersectWith(b); 
foreach (T t in a.Concat(b)) 
{ 
    if (!data.Contains(t)) 
    { 
     yield return t; 
    } 
} 

này có sự khác biệt quan trọng sau đây:

  • Cả ab được lặp lại hai lần. Trong một số trường hợp có thể là một điều rất xấu - bạn có thể gọi số ToList trên mỗi trường hợp để bắt đầu giữ lại bộ đệm.
  • Nếu có các trùng lặp trong số a hoặc b, chúng sẽ được tạo nhiều lần. Nếu bạn muốn tránh điều này, bạn có thể giữ một tập hợp các giá trị đã mang lại. Tại thời điểm này, nó sẽ tương đương với:

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

Con số này vẫn chỉ hai thiết lập hoạt động thay vì ba trong mã gốc của bạn mặc dù.

+0

Cảm ơn Jon đã trả lời nhanh chóng của bạn. HashSet làm việc tốt miễn là bạn không quan tâm đến thứ tự ban đầu của các mục. Điều gì sẽ xảy ra nếu tôi muốn giữ thứ tự của các mục trong 'a' và' b' trong sự khác biệt? –

+0

@Pierre: Tôi đã chỉnh sửa câu trả lời của mình với một vài tùy chọn khác. –

+0

Cảm ơn rất nhiều vì đã dành thời gian cho bạn. Trong trường hợp của tôi, 'a' và' b' không chứa các bản sao, vì vậy đây không phải là một mối quan tâm. Biểu thức LINQ mà bạn đề xuất dễ đọc hơn nhiều (và do đó có thể duy trì được) so với đoạn mã liên quan đến 'HashSet'. Tôi thích nó! –

3

Cho a.Except (b) và b.Except (a) rời rạc, bạn có thể sử dụng concat thay vì union, lưu toán tử đã đặt (và concat hiệu quả hơn).

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

Điều này vẫn chạy qua từng danh sách hai lần.

+0

Cảm ơn bạn; bạn nói đúng, 'Concat' sẽ nhanh hơn' Union' vì đầu vào của tôi là rời rạc; Tôi đã bỏ qua điểm đó. –

0

Chúng tôi đã có một nhu cầu tương tự cho một dự án trong công ty của tôi, vì vậy chúng tôi đã viết phần mở rộng này:

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

Nó so sánh các yếu tố của hai bộ sưu tập (sử dụng một IEqualityComparer hay không, tùy theo lựa chọn của bạn).

  • Các đối tượng trở về, một EnumerablePair<T>, chứa các đối tượng có trong leftOperand hoặc rightOperand, nhưng không phải cả hai (XOR).
  • EnumerablePair<T>.Left chứa các đối tượng nằm trong số leftOperand nhưng không có trong rightOperand.
  • EnumerablePair<T>.Right chứa các đối tượng nằm trong số rightOperand nhưng không có trong leftOperand.

Bạn có thể sử dụng phần mở rộng như thế này:

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

xorList, leftXorrightXorIEnumerable<T>.

Các vấn đề liên quan