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

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

4

3 回答 3

3

我一直在使用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));
        }
    }
}
于 2013-05-16T22:32:46.310 回答
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 2 ) 时间内运行的。因此,您可能想要实现 anIEqualityComparer<Foo>并将您的第二个列表放入 a 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 源代码并不难,它是助手并将它们调整为产品左右连接运算符,而不是股票内部连接。

于 2013-05-16T23:24:47.340 回答
0

这对您的目的有用吗?

var missing = List1.Except(List2);

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

于 2013-05-16T21:56:15.297 回答