4

複雑な式で生成された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を変更して、見逃した新しいものを生成する方法はありますか?

4

2 に答える 2

4

(クエリが与えられた場合、実際には姓でフィルタリングしたかったと仮定します。)

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());

必要と思われるよりも少し多くの作業を行っていますが、それほど悪くはありません。

編集: コメントでのアニとの議論は読む価値があります。基本的に、私たちはすでにすべての人を繰り返し処理しています - したがって、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化されるため、実際にどのように問題が発生するかを確認するのは困難です...

于 2011-01-10T19:58:04.497 に答える
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));

これにより、re-flattening-filtering-ToLookupを回避したり、新しい辞書を作成したりすることを回避できます(したがって、キーを再度ハッシュすることもできます)。

IGrouping2番目のクエリの場合、アイデアは似ています。全体ではなく、の要素に対してフィルタリングするのではなく、似たようなクラスを作成する必要がありますIGrouping

ただのアイデア、多分それは他の方法より速くすることができなかったでしょう:)

于 2011-01-10T20:43:33.610 に答える