Создание ILookups - PullRequest
       17

Создание ILookups

4 голосов
/ 10 января 2011

У меня есть 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 или выполнения компьютером глупой работы (перегруппировки уже сгруппированных), есть ли способ изменить существующие ILookups для создания новых, которые я пропустил?

Ответы [ 2 ]

4 голосов
/ 10 января 2011

(я предполагаю, что вы на самом деле хотели фильтровать по фамилии, учитывая ваш запрос.)

Вы не можете изменить любую реализацию 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());

Хотя он все еще выполняет на бит больше работы, чем мы могли быдумаю, что это не так уж плохо.

РЕДАКТИРОВАТЬ: обсуждение с Ани в комментариях стоит прочитать.По сути, мы все равно собираемся перебирать каждого человека - поэтому, если мы предполагаем поиск и вставку словаря 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 материализует запрос, трудно понять, как он на самом деле может пойти не так ...

2 голосов
/ 10 января 2011

Для вашего первого запроса, как насчет реализации вашего собственного FilteredLookup, способного воспользоваться преимуществом прихода от другого ILookup?
(спасибо Джону Скиту за подсказку)

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

Это позволит вам избежать повторного запроса.-flattens-filtering-ToLookup или создание нового словаря (и снова хэширования ключей).

Для второго запроса идея будет аналогичной, вам просто нужно создать аналогичный класс без фильтрации для всего IGrouping но для элементов IGrouping.

Просто идея, может быть, она не может быть быстрее, чем другие методы:)

...