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