题
我有一个复杂的表达产生的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和iGroup界面,或者使计算机做愚蠢的工作(重组已经分组的内容)之外,是否有一种方法可以改变现有的ilookup来生成我错过的新的iLookup?
解决方案
(我将假设您实际上想用姓氏过滤,给定查询。)
您无法修改任何实现 ILookup<T>
我知道。当然有可能 实施 ToLookup
带着不变的查找, ,正如您清楚地知道:)
你什么 可以 但是,要更改以使用 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
实现查询,很难看到它实际上可能出错...
其他提示
对于您的第一个查询,如何实施自己的 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));
这使您可以避免重新填充过滤器,或者创建新的词典(以及再次放哈希键)。
对于第二个查询,想法将是相似的,您应该只创建一个类似的类,而不是整体上过滤 IGrouping
但是对于 IGrouping
.
只是一个想法,也许不能比其他方法更快:)