如何基于其他谓词而不是基于相等来进行GroupJoin?

时间:2015-04-10 09:04:16

标签: c# linq

我想在两个集合之间做一个GroupJoin但是基于一些其他谓词而不是相等。例如,如果我有一个带有项目的集合,每个集合都包含一个范围属性,我想将每个项目与来自另一个集合的项目相关联,这些项目具有一定范围内的值的属性。 可以使用GroupJoin或任何其他LINQ方法完成此操作吗? Two collections with resulting groups

3 个答案:

答案 0 :(得分:1)

不幸的是,IEqualityComparer<T>是修改GroupJoin中分组逻辑的最佳方式,只允许比较两个T

这意味着您有两种选择:

  1. 使类型相同 - 您的范围可以是Tuple<int, int>Item1是最小值,Item2是最大值,您的值可以属于同一类型,Item1为值,Item2为...值。然后你可以编写一个处理它们的相等比较器。它非常糟糕,我真的不会这么做。但它会奏效。
  2. 实施您自己的GroupJoin
  3. 方法

    第二种方法很简单,就像这样。

    这是一个快速而肮脏的实现,有一些不那么复杂的排名。但它应该作为一个有效的概念证明。

    public static IEnumerable<IGrouping<TKey, TValue>> GroupJoin<TKey, TValue>(this IEnumerable<TValue> values, IEnumerable<TKey> keys, Func<TKey, TValue, bool> predicate)
    {       
        return values.SelectMany(v => keys, (v, k) => new { v, k })
                     .Where(c => predicate(c.k, c.v))
                     .GroupBy(c => c.k, c => c.v);
    }
    

    我确定这里有一些黑魔法,但在我的示例实现中,我基本上只是交叉加入集合,然后抓住哪个匹配。

    在这方面肯定会有一些优化,特别是如果您确定一个值只会映射到一个键。如果你肯定知道你正在处理必须符合范围的整数,你可能会做得更好一些事情,但我得到的印象是你一般都会问,所以这是一个通用答案。

    为了解决你的例子,那么,你正在寻找这样的事情:

    var keys = new Tuple<int, int>[] { Tuple.Create(1, 5), Tuple.Create(5, 10) };
    
    var array = new[] { 3, 4, 7, 9 };
    
    var groups = array.GroupJoin(keys, (a, b) => a.Item1 <= b && a.Item2 > b);
    

答案 1 :(得分:1)

假设这些是您的数据类型:

public class Range
{
    public int Start { get; set; }
    public int End { get; set; }
}

public class Item
{
    public int Number { get; set; }
}

此Linq表达式将为您提供所需内容(包括重叠范围)

var ranges = new Range[];
var items = new Item[];

// ...

var rangeGroups = ranges
    .Select(r=> new {Range=r, Items=items.Where(i=> (r.Start <= i.Number) && (i.Number <= r.End))});

rangeGroups每个项目都有RangeItems

查看此在线演示 - https://ideone.com/HQomfc

答案 2 :(得分:0)

您可以使用IEqualityComparer<T>重叠:https://msdn.microsoft.com/en-us/library/bb535047(v=vs.95).aspx

Offcourse,这意味着T必须是一个具体的类型,这导致样板重的代码。你会得到像

这样的东西
public class Foo {
  public int min;
  public int max;
}

public class Bar {
  public int value;
}

private class FooBarJoinKey {
  public Foo foo;
  public Bar bar;
}

private class FooBarJoinCondition : IEQualityComparer<FooBarJoinKey> {
  public bool Equals(FooBarJoinKey left, FooBarJoinKey right){
    Foo foo = left.foo ?? right.foo;
    Bar bar = right.bar ?? left.bar;
    return (foo.min <= bar.value &&
           foo.max >= bar.value)

  }

  //If Equals returns true, the HashKeys of both objects *have* to be equal.
  //There is no way to guarantee this other than it being constant
  public int GetHashCode(FooBarJoinKey dummy){ return 0;}
}

使用示例:

IEnumerable<Foo> foos = ???
IEnumerable<Bar> bars = ???
Func<Foo, IEnumerable<Bar>> resultselector = ???
var comparer = new FooBarJoinCondition();

var grouped = foos.GroupJoin(bars, foo => new FooBarJoinKey(){ foo = foo;}, bar => new FooBarJoinKey() { bar = bar ;}, resultselector, comparer);

这是一个非常糟糕的解决方案。据我所知,这也是唯一的方法。