質問
複雑な式によって生成された ILookup があります。姓による人々の検索だとしましょう。(私たちの単純化した世界モデルでは、姓は家族ごとに一意です)
ILookup<string, Person> families;
現在、作成方法に興味のあるクエリが 2 つあります。
まず、姓でフィルタリングするにはどうすればよいでしょうか?
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())));
ここで 2 つの問題に直面します。の Grouping
型が存在しません (内部内部クラスとしての場合を除く) Lookup
)、たとえそうなったとしても、上で説明した問題が発生するでしょう。
それでは、ILookup インターフェイスと IGrouping インターフェイスを完全に実装するか、コンピューターに愚かな量の作業 (既にグループ化されているものを再グループ化する) を実行させる以外に、既存の ILookup を変更して、見逃していた新しい ILookups を生成する方法はあるのでしょうか?
解決
(私はあなたがあなたのクエリを与えられて、あなたが実際に姓でフィルタリングしたいと思っていると思います。)
の実装を変更することはできません ILookup<T>
私が知っていること。確かに可能です 埋め込む ToLookup
不変の検索で, 、あなたが明らかに知っているように:)
あなたは何 たぶん......だろう ただし、使用するために変更することです Dictionary<string, List<Person>>
:
var germanFamilies = families.Where(family => IsNameGerman(family.Key))
.ToDictionary(family => family.Key,
family.ToList());
そのアプローチは、2回目のクエリにも機能します。
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));
これにより、ToLookup によるフィルタリングの再平坦化や新しい辞書の作成 (キーの再ハッシュ) を回避できます。
2 番目のクエリでも考え方は似ています。全体をフィルタリングしない同様のクラスを作成するだけです。 IGrouping
しかし、の要素については、 IGrouping
.
単なるアイデアですが、おそらく他の方法よりも高速ではないかもしれません:)