Вопрос
У меня есть 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
.
Просто идея, может быть, это не может быть быстрее, чем другие методы :)