【问题标题】:What is the cleanest way to do an outer join without equals?在没有等于的情况下进行外部连接的最干净的方法是什么?
【发布时间】:2013-05-11 23:01:20
【问题描述】:

我有两个列表,我需要在第一个列表中找到第二个列表中缺少的项目,但我只能将它们与布尔函数进行比较。

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 这样的外连接。有没有更清晰的方法来做到这一点?

有一种采用比较运算符的 GroupJoin 形式,但我不清楚是否有办法使用它来进行外连接。

【问题讨论】:

    标签: c# linq outer-join


    【解决方案1】:

    这对您的目的有用吗?

    var missing = List1.Except(List2);
    

    如果您需要自定义比较逻辑,您可以构建自定义 IEqualityComparer。但请注意,Except 将两个列表都视为集合,因此它将消除 List1 中的重复项。

    【讨论】:

    • 我需要自定义比较逻辑,不能直接编辑A。更复杂的是,A.Equals 已经被一种不同于我需要的比较运算符覆盖。
    【解决方案2】:

    我一直在使用一些有用(而且很短!)的代码from a blog by Ed Khoze

    他发布了一个方便的类,它提供了一个适配器,以便您可以将Enumerable.Except() 与 lambda 一起使用。

    获得该代码后,您可以使用Except() 来解决您的问题,如下所示:

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

    这是一个完整的可编译示例。感谢 Ed 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));
            }
        }
    }
    

    【讨论】:

    • 如果您必须多次执行并且值得所有设置,这是一个非常好的解决方案。
    【解决方案3】:

    如果你的问题陈述实际上是

    查找 X 中不存在于 Y 中的所有成员

    给定一个实现IEquatable&lt;Foo&gt; 的类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(N2) 时间内运行的。因此,您可能希望实现 IEqualityComparer&lt;Foo&gt; 并将您的第二个列表放入 HashSet&lt;Foo&gt;

    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 源并不难,并将它们调整为产品左右连接运算符而不是股票内连接。

    【讨论】:

    • 到达那里...我无法覆盖 Foo,但我可以编写一个新的 FooComarer,它的开销比 LINQHelper 解决方案略少。
    猜你喜欢
    • 2014-05-03
    • 1970-01-01
    • 2017-10-27
    • 2013-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-02
    • 2011-08-19
    相关资源
    最近更新 更多