4

我有一个由一些复杂的表达式生成的 ILookup。假设这是按姓氏查找人。(在我们简单的世界模型中,姓氏在家庭中是唯一的)

ILookup<string, Person> families;

现在我有两个对如何构建感兴趣的查询。

首先,我将如何按姓氏过滤?

var germanFamilies = families.Where(family => IsNameGerman(family.Key));

但在这里,germanFamilies是一个IEnumerable<IGrouping<string, Person>>;如果我调用ToLookup()它,我最好打赌会得到一个IGrouping<string, IGrouping<string, Person>>. 如果我尝试变得聪明并SelectMany先打电话,我最终会让电脑做很多不必要的工作。您将如何轻松将此枚举转换为查找?

其次,我只想查找成年人。

var adults = families.Select(family =>
         new Grouping(family.Key, family.Select(person =>
               person.IsAdult())));

这里我面临两个问题:该Grouping类型不存在(除了作为 的内部内部类Lookup),即使它存在,我们也会遇到上面讨论的问题。

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

4

2 回答 2

4

(根据您的查询,我假设您实际上想按姓氏过滤。)

您无法修改ILookup<T>我所知道的任何实现。正如您清楚地知道的那样,当然可以使用不可变的查找来实现:)ToLookup

但是,您可以做的是更改为使用 a Dictionary<string, List<Person>>

var germanFamilies = families.Where(family => IsNameGerman(family.Key))
                             .ToDictionary(family => family.Key,
                                           family.ToList());

该方法也适用于您的第二个查询:

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);

那么这可能会更有效率(如果我们每个家庭中有很多人),但这意味着我们正在“脱离上下文”使用分组。我相信这实际上没问题,但出于某种原因,它在我的嘴里留下了一点奇怪的味道。随着ToLookup查询的具体化,很难看出它实际上是如何出错的......

于 2011-01-10T19:58:04.497 回答
2

对于您的第一个查询,如何实现您自己的FilteredLookup能够利用来自另一个的优势ILookup
(感谢 Jon Skeet 的提示)

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);
}

FilteredLookup为:

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));

这使您可以避免重新展平过滤ToLookup,或创建新字典(以及再次散列键)。

对于第二个查询,想法将是相似的,您应该只创建一个类似的类,而不是过滤整体IGrouping,而是过滤IGrouping.

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

于 2011-01-10T20:43:33.610 回答