Вопрос

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

Это было полезно?

Решение

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

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

Это позволяет избежать повторного выравнивания-фильтрации - Tolookup или создания нового словаря (и снова включить ключи).

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top