创建ILookup

时间:2011-01-10 19:43:18

标签: linq ilookup

我有一个由一些复杂的表达式生成的ILookup。让我们说这是按姓氏查找人物。 (在我们简单化的世界模型中,姓氏是由家族独有的)

ILookup<string, Person> families;

现在我有两个问题,我对如何构建感兴趣。

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

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

但是在这里,germanFamiliesIEnumerable<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来生成我错过的新的?< / p>

2 个答案:

答案 0 :(得分:4)

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

您无法修改我所知道的ILookup<T>的任何实现。当你清楚地意识到implement ToLookup with an immutable lookup时,肯定是可能的。)

Dictionary<string, List<Person>>

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

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

虽然那仍然比我们认为必要的更多工作,但这并不算太糟糕。

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

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); 实现了查询,但很难看出它实际上是如何出错的......

答案 1 :(得分: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的元素进行过滤。

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