Linq 创建ILookups

Linq 创建ILookups,linq,ilookup,Linq,Ilookup,我得到了一个由复杂表达式生成的ILookup。让我们假设这是一个按姓氏查找的人。(在我们过于简单的世界模型中,姓氏因家庭而异) 但在这里,germanFamilies是一个IEnumerable;如果我在上面调用ToLookup(),我最好得到一个I分组。如果我想聪明点,先打电话给SelectMany,结果电脑会做很多不必要的工作。如何轻松地将此枚举转换为查找 第二,我只想找成年人 这里我面临两个问题:分组类型不存在(除了作为查找的内部类之外),即使存在,我们也会有上面讨论的问题 那么,除了完全

我得到了一个由复杂表达式生成的ILookup。让我们假设这是一个按姓氏查找的人。(在我们过于简单的世界模型中,姓氏因家庭而异)

但在这里,
germanFamilies
是一个
IEnumerable
;如果我在上面调用
ToLookup()
,我最好得到一个
I分组
。如果我想聪明点,先打电话给
SelectMany
,结果电脑会做很多不必要的工作。如何轻松地将此枚举转换为查找

第二,我只想找成年人

这里我面临两个问题:
分组
类型不存在(除了作为
查找
的内部类之外),即使存在,我们也会有上面讨论的问题

那么,除了完全实现ILookup和IGrouping接口,或者让计算机做一些愚蠢的工作(重新组合已经分组的内容)之外,有没有办法改变现有的ILookup以生成我遗漏的新ILookup?

(我假设您确实想根据您的查询按姓氏进行筛选。)

您不能修改我所知道的
ILookup
的任何实现。正如您清楚地意识到的那样,当然有可能:)

但是,您可以做的是更改为使用
词典

这种方法也适用于第二个查询:

var adults = families.ToDictionary(family => family.Key,
                                   family.Where(person => persion.IsAdult)
                                         .ToList());
虽然这仍然比我们认为必要的多做了一些工作,但也不算太糟糕

编辑:评论中与Ani的讨论值得一读。基本上,我们已经对每个人进行了迭代-因此,如果我们假设O(1)字典查找和插入,那么在时间复杂性方面,使用现有查找实际上并不比平坦化更好:

var adults = families.SelectMany(x => x)
                     .Where(person => person.IsAdult)
                     .ToLookup(x => x.LastName);
在第一种情况下,我们可能会使用现有的分组,如下所示:

// We'll have an IDictionary<string, IGrouping<string, Person>>
var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key);
//我们将有一个IDictionary
var germanFamilies=families.Where(family=>isnamederman(family.Key))
.ToDictionary(family=>family.Key);

这可能会更有效率(如果我们每个家庭都有很多人的话),但这意味着我们在“断章取义”地使用分组。我相信这其实没关系,但出于某种原因,它在我嘴里留下了一点奇怪的味道。当
ToLookup
具体化查询时,很难看出它实际上是如何出错的…

对于您的第一个查询,如何实现您自己的
FilteredLookup
以利用来自另一个
ILookup

(感谢Jon Skeet的提示)

和分组:

internal sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly TKey key;
    private readonly IEnumerable<TElement> elements;

    internal Grouping(TKey key, IEnumerable<TElement> elements)
    {
        this.key = key;
        this.elements = elements;
    }

    public TKey Key { get { return key; } }

    public IEnumerator<TElement> GetEnumerator()
    {
        return elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
这允许您避免对ToLookup进行重新扁平化过滤,或创建新字典(因此再次对键进行哈希)

对于第二个查询,想法是类似的,您应该只创建一个类似的类,不过滤整个
i分组
,而是过滤
i分组
的元素


只是一个想法,也许它不可能比其他方法更快:)

我正要发布类似的东西,但想一想,我真的不相信这比天真的re-ToLookup更有效,至少在时间复杂性的意义上。给定一个人,你应该能够在固定的时间内把他/她推入正确的桶中。因此,原始查找的平坦化实际上并不需要太多成本。在这两种情况下,大部分工作都应该是通过每个人进行枚举、分配、动态缓冲区大小调整、复制等。@Ani:我怀疑这还是非常有效的。例如,我认为IGrouping的MS实现也实现了
IList
,因此
ToList
调用可以使用批量操作(在第一种情况下,而不是第二种情况下)。为什么要再次对每个项执行哈希查找,而不是对每个族执行一次?就复杂性而言,它可能不会更好,但这并不意味着它不值得做。这些恒定的因素可能很重要:)足够公平,但我想提醒OP关于微观优化。在这种情况下,你本质上是在建议他放弃他(相当合理地)所认为的数据的语义理想表示,而去做其他事情,以获得可能并不十分显著的性能提升。我个人并不认为这种方法有问题,也不认为使用
字典而不是查找有问题。
ILookup
界面目前不是很有用。:)@阿尼:我在第一个问题中遗漏了一些东西——我们根本不需要给托利斯打电话;我们可以保持分组。@Jon Skeet,@Ani:你觉得我的答案怎么样?我想我错过了一些东西…很好。这是处理第一个查询的好方法,尤其是因为过滤器很便宜。对于第二个查询,类似的技术在记忆方面要聪明得多。我还没有完全看过,但看起来不错。一个问题:你不能记住过滤组的列表,而不仅仅是过滤计数吗?这可以被
Count
以及
GetEnumerator
重用。如果您将其存储到
字典
(或类似工具)中,它甚至可以被
包含的内容
共享。当然,为了节省空间,您应该放弃保留对原始查找的引用。这件事很快就变得复杂起来本质上,我所建议的是一种策略,它会转移到Jon Skeet的答案,并在你知道你必须对所有东西运行过滤器的那一刻停留在那里。@Ani:是的,记忆可能是个好主意,可能有点复杂:)
var adults = families.ToDictionary(family => family.Key,
                                   family.Where(person => persion.IsAdult)
                                         .ToList());
var adults = families.SelectMany(x => x)
                     .Where(person => person.IsAdult)
                     .ToLookup(x => x.LastName);
// We'll have an IDictionary<string, IGrouping<string, Person>>
var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key);
public static ILookup<TKey, TElement> ToFilteredLookup<TKey, TElement>(this ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
{
    return new FilteredLookup<TKey, TElement>(lookup, filter);
}
internal sealed class FilteredLookup<TKey, TElement> : ILookup<TKey, TElement>
{
    int count = -1;
    Func<IGrouping<TKey, TElement>, bool> filter;
    ILookup<TKey, TElement> lookup;

    public FilteredLookup(ILookup<TKey, TElement> lookup, Func<IGrouping<TKey, TElement>, bool> filter)
    {
        this.filter = filter;
        this.lookup = lookup;
    }

    public bool Contains(TKey key)
    {
        if (this.lookup.Contains(key))
            return this.filter(this.GetGrouping(key));
        return false;
    }

    public int Count
    {
        get
        {
            if (count >= 0)
                return count;
            count = this.lookup.Where(filter).Count();
            return count;
        }
    }

    public IEnumerable<TElement> this[TKey key]
    {
        get
        {
            var grp = this.GetGrouping(key);
            if (!filter(grp))
                throw new KeyNotFoundException();
            return grp;
        }
    }

    public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
    {
        return this.lookup.Where(filter).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    private IGrouping<TKey, TElement> GetGrouping(TKey key)
    {
        return new Grouping<TKey, TElement>(key, this.lookup[key]);
    }
}
internal sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private readonly TKey key;
    private readonly IEnumerable<TElement> elements;

    internal Grouping(TKey key, IEnumerable<TElement> elements)
    {
        this.key = key;
        this.elements = elements;
    }

    public TKey Key { get { return key; } }

    public IEnumerator<TElement> GetEnumerator()
    {
        return elements.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
var germanFamilies = families.ToFilteredLookup(family => IsNameGerman(family.Key));