Lambda 中的自定义交集

12
我想知道是否可以使用lambda表达式解决这个问题:
List<Foo> listOne = service.GetListOne();
List<Foo> listTwo = service.GetListTwo();
List<Foo> result = new List<Foo>();

foreach(var one in listOne)
{
    foreach(var two in listTwo)
    {
        if((one.Id == two.Id) && one.someKey != two.someKey)
           result.Add(one);
    }
}
4个回答

14
当然可以!您可以使用Linq的Intersect扩展方法的这个重载版本,它接受一个IEqualityComparer<T>,像这样:
public class FooComparer : IEqualityComparer<Foo> 
{
    public bool Equals(Foo x, Foo y)
    {
        return x.Id == y.Id && x.someKey != y.someKey;
    }

    public int GetHashCode(Foo x)
    {
        return x.Id.GetHashCode();
    }
}

...

var comparer = new FooComparer();
List<Foo> listOne = service.GetListOne();
List<Foo> listTwo = service.GetListTwo();
List<Foo> result = listOne.Intersect(listTwo, comparer).ToList();

@Johan 我相信在不同的集合大小下,可能会有一些性能差异。当然,如果你实现 IEqualityComparer 的方式不是完全对称的话,也会产生影响。 - p.s.w.g
1
@Johan 我不记得有关性能调优的具体细节,所以如果这是一个性能关键的应用程序,你可能需要先运行一些测试。 - p.s.w.g
1
关于intersect中的第二个参数:我是否需要将其放在实现IEqualityComparer的单独类中? - Johan
@Johan 是的。IEqualityComparer<T>是一个接口,而不是委托。我为之前的错误感到抱歉。请查看我的更新答案以获取示例实现。 - p.s.w.g
@p.s.w.g 我在 FooComparer 后面错过了 <T>。非常感谢你的帮助! - Johan
显示剩余5条评论

5
listOne.SelectMany(x=>listTwo.Where(y=>x.Id==y.Id && x.someKey != y.someKey));

1
看起来Johan很关心性能。虽然这是他最初代码的直接翻译,但它也遭受了相同的O(n x m)复杂度。这是到目前为止给出的所有答案中效率最低的。 - Kris Vandermotten
为什么不呢?使用join的解决方案比他原来的代码更有效率。虽然他可以编写比Linq解决方案更高效的代码,但这可能需要很长时间,并导致难以阅读和维护的代码。 - Kris Vandermotten
1
@KrisVandermotten的问题是“使用lambda表达式解决”..OP从未提到过性能..! - Anirudha
我并没有说你的答案不好。我只是为了OP和其他读者的利益而向其中添加了信息。而且我之所以说“似乎”是因为他在评论其他答案时问到了性能问题。 - Kris Vandermotten
1
.SelectMany正在执行嵌套循环。.Join正在执行哈希查找。我建议您在大型列表上对两个答案进行基准测试。 - Kris Vandermotten

2
var result = from one in listOne
             join two in listTwo on one.Id equals two.Id
             where one.SomeKey != two.SomeKey
             select one;

更新:似乎有些人认为这并没有回答问题,因为它据说没有使用Lambda表达式。当然,它是在使用友好的查询语法中使用Lambda表达式。
下面是完全相同的代码,只是不那么易读:
var result = 
    listOne.Join(listTwo, one => one.Id, two => two.Id, (one, two) => new { one, two })
           .Where(p => p.one.someKey != p.two.someKey)
           .Select(p => p.one);

2

您可以尝试以下方法:

var result = listOne.Join(listTwo,
    (one) => one,
    (two) => two,
    (one, two) => one,
    new MyFooComparer());

其中,MyFooComparer可能看起来像这样:

class MyFooComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        return x.Id == y.Id && x.someKey != y.someKey;
    }

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

[更新]

我对IntersectJoin的性能很感兴趣,因此我对我的解决方案和@p.s.w.g.的进行了小型性能比较(listOnelistTwo每个都有10个项目):

var comparer = new MyFooComparer();
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100000; i++)
{
    var result1 = listOne.Intersect(listTwo, comparer).ToList();
}
Console.WriteLine("Intersect: {0}",sw.Elapsed);
sw.Restart();
for (int i = 0; i < 100000; i++)
{
    var result = listOne.Join(listTwo,
        (one) => one,
        (two) => two,
        (one, two) => one,
        comparer);
}
Console.WriteLine("Join:      {0}", sw.Elapsed);

输出结果:
Intersect: 00:00:00.1441810
Join:      00:00:00.0037063

.NET 会以某种方式调整代码。最好单独运行这些测试,并在一段时间内进行预热。这不是基准测试的正确方法。 - Anirudha
@Anirudh,我完全同意,这不是测试的最佳方式。但即使颠倒顺序,结果也是相同的。 - Alex Filipovici

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接