没有等于进行外连接的最简洁方法是什么?

时间:2013-05-16 21:29:39

标签: c# linq outer-join

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

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形式采用比较运算符,但我不清楚是否有办法使用它来进行外连接。

3 个答案:

答案 0 :(得分:3)

我一直在使用一些有用的(和简短!)代码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));
        }
    }
}

答案 1 :(得分:2)

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

  

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

给定一个实现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 2 )时间内运行的。因此,您可能希望实施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连接操作的来源,这就接近它的作用。

剥离Linq源的Join()和它的助手并将它们调整为产品左右连接运算符而不是库存内连接并不困难。

答案 2 :(得分:0)

这是否适用于您的目的?

var missing = List1.Except(List2);

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