我有一个由一些复杂的表达式生成的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来生成我错过的新的?< / p>
答案 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
的元素进行过滤。
只是一个想法,也许它不会比其他方法更快:)