2013-05-16 50 views
2

我有兩個列表,我需要找到第二個列表中缺少的項目,但我只能將它們與布爾函數進行比較。什麼是最簡潔的方式來做一個沒有等號的外連接?

class A 
{ 
    internal bool Matching(A a) 
    { 
     return true; 
    } 
} 

class OuterMatch 
{ 
    List<A> List1 = new List<A>(); 
    List<A> List2 = new List<A>(); 

    void BasicOuterJoin() 
    { 
     // textbook example of an outer join, but it does not use my Matching function 
     var missingFrom2 = from one in List1 
          join two in List2 
          on one equals two into matching 
          from match in matching.DefaultIfEmpty() 
          where match == null 
          select one; 
    } 

    void Matching() 
    { 
     // simple use of the matching function, but this is an inner join. 
     var matching = from one in List1 
         from two in List2 
         where one.Matching(two) 
         select one; 
    } 

    void MissingBasedOnMatching() 
    { 
     // a reasonable substitute for what I'm after 
     var missingFrom2 = from one in List1 
          where (from two in List2 
            where two.Matching(one) 
            select two) 
            .Count() == 0 
          select one; 
    } 

MissingBasedOnMatching給了我正確的結果,但它看起來並不明顯外連接一樣BasicOuterJoin是。有沒有更清楚的方法來做到這一點?

有羣組加入的一種形式,需要一個比較運營商,但我不清楚是否有使用它來使外部聯接的方式。

回答

2

如果您的問題聲明實際上是

求x的所有成員不Y中

存在,並給予類Foo實現IEquatable<Foo>(幾乎你Matching方法做什麼):

class Foo : IEquatable<Foo> 
{ 
    public bool Equals(Foo other) 
    { 
     throw new NotImplementedException(); 
    } 
} 

那麼這個代碼應該給你你想要的東西:

List<Foo> x  = GetFirstList() ; 
List<Foo> y  = GetSecondList() ; 
List<Foo> xNotInY = x.Where(xItem => ! y.Any(yItem => xItem.Equals(yItem))).ToList() ; 

你應該記住,這運行在O(N )時間。因此,你可能想實現一個IEqualityComparer<Foo>,放在一個HashSet<Foo>你的第二個列表:

class FooComparer : IEqualityComparer<Foo> 
{ 
    public bool Equals(Foo x, Foo y) 
    { 
     if (x == null) 
     { 
      return y == null ; 
     } 
     else if (y == null) return false ; 
     else 
     { 
      return x.Equals(y) ; 
     } 
    } 

    public int GetHashCode(Foo obj) 
    { 
     return obj.GetHashCode() ; 
    } 
} 

然後像做

List<Foo> x  = GetFirstList() ; 
List<Foo> y  = GetSecondList() ; 
HashSet<Foo> yLookup = new HashSet<Foo>(y , new FooComparer()) ; 
List<Foo> xNotInY = x.Where(x => !yLookup.Contains(x)) ; 

你會在構建哈希集合招致一些開銷(1次通過第二個列表),但後續查找通過Contains()是O(1)。

如果你看看來源爲Linq的連接操作,這是接近它做什麼。

要解除Join()的Linq源代碼並將其調整爲產品左右連接運算符而不是股票內部連接,並不困難。

+0

到達那裏......我無法覆蓋Foo,但我可以編寫一個新的FooComarer,並且它比LINQHelper解決方案的開銷稍小。 –

0

這是否適合您的用途?

var missing = List1.Except(List2); 

如果您需要自定義比較邏輯,則可以構建自定義IEqualityComparer。但請注意Except將這兩個列表視爲集合,因此它將消除來自List1的重複項。

+0

我需要自定義比較邏輯,我不能直接編輯A.使事情進一步複雜化的是,A.Equals已經被一個不同的比較運算符覆蓋,而不是我需要的。 –

3

我已經使用了一些有用的(短!)代碼from a blog by Ed Khoze

他貼一個方便的類,它提供了一個適配器,以便您可以使用Enumerable.Except()以拉姆達。

一旦你的代碼,你可以使用Except()解決您的問題,像這樣:

var missing = list1.Except(list2, (a, b) => a.Matching(b)); 

下面是一個完整編譯樣本。感謝埃德Khoze爲LINQHelper類:

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace Demo 
{ 
    class A 
    { 
     public int Value; 

     public bool Matching(A a) 
     { 
      return a.Value == Value; 
     } 

     public override string ToString() 
     { 
      return Value.ToString(); 
     } 
    } 

    class Program 
    { 
     void test() 
     { 
      var list1 = new List<A>(); 
      var list2 = new List<A>(); 

      for (int i = 0; i < 20; ++i) list1.Add(new A {Value = i}); 
      for (int i = 4; i < 16; ++i) list2.Add(new A {Value = i}); 

      var missing = list1.Except(list2, (a, b) => a.Matching(b)); 

      missing.Print(); // Prints 0 1 2 3 16 17 18 19 
     } 

     static void Main() 
     { 
      new Program().test(); 
     } 
    } 

    static class MyEnumerableExt 
    { 
     public static void Print<T>(this IEnumerable<T> sequence) 
     { 
      foreach (var item in sequence) 
       Console.WriteLine(item); 
     } 
    } 

    public static class LINQHelper 
    { 
     private class LambdaComparer<T>: IEqualityComparer<T> 
     { 
      private readonly Func<T, T, bool> _lambdaComparer; 
      private readonly Func<T, int> _lambdaHash; 

      public LambdaComparer(Func<T, T, bool> lambdaComparer) : 
       this(lambdaComparer, o => 0) 
      { 
      } 

      private LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash) 
      { 
       if (lambdaComparer == null) 
        throw new ArgumentNullException("lambdaComparer"); 
       if (lambdaHash == null) 
        throw new ArgumentNullException("lambdaHash"); 
       _lambdaComparer = lambdaComparer; 
       _lambdaHash = lambdaHash; 
      } 
      public bool Equals(T x, T y) 
      { 
       return _lambdaComparer(x, y); 
      } 
      public int GetHashCode(T obj) 
      { 
       return _lambdaHash(obj); 
      } 
     } 

     public static IEnumerable<TSource> Except<TSource> 
     (
      this IEnumerable<TSource> enumerable, 
      IEnumerable<TSource> second, 
      Func<TSource, TSource, bool> comparer 
     ) 
     { 
      return enumerable.Except(second, new LambdaComparer<TSource>(comparer)); 
     } 
    } 
} 
+0

這是一個非常好的解決方案,如果你必須多次完成並且值得所有的設置。 –

相關問題