Linq - конвертировать ILookup в другой ILookup

Это должно быть просто, но я не могу придумать хороший способ сделать это. Как вы преобразуете ILookup в другой ILookup? Например, как бы вы скопировали / клонировали ILookup, создав другой ILookup с теми же ключами и теми же группами?

Вот моя неудачная попытка:

static ILookup<TKey, TValue> Copy<TKey, TValue>(ILookup<TKey, TValue> lookup)
{
    return lookup
        .ToDictionary(
            grouping => grouping.Key,
            grouping => grouping.ToArray())
        .SelectMany(pair =>
            pair
                .Value
                .Select(value =>
                    new KeyValuePair<TKey, TValue>(pair.Key, value)))
        .ToLookup(pair => pair.Key, pair => pair.Value);
}

Кто-нибудь может улучшить это?

- Брайан

2 ответа

Решение

Делает ли это то, что вы хотите?

static ILookup<TKey, TValue> Copy<TKey, TValue>(ILookup<TKey, TValue> lookup)
{
    return lookup.
           SelectMany(g => g,
                     (g, v) => new KeyValuePair<TKey, TValue>(g.Key, v)).
           ToLookup(kvp => kvp.Key, kvp => kvp.Value);
}

Конечно, если вы хотите как-то преобразовать значения, возможно, вы хотите что-то вроде этого:

static ILookup<TKey, TValueOut> Transform<TKey, TValue, TValueOut>(
       ILookup<TKey, TValue> lookup,
       Func<TValue, TValueOut> selector)
{
    return lookup.
           SelectMany(g => g,
                      (g, v) => new KeyValuePair<TKey, TValueOut>(g.Key, selector(v))).
           ToLookup(kvp => kvp.Key, kvp => kvp.Value);
}

Обратите внимание, что этот метод содержит промежуточные значения в KeyValuePair который, будучи типом значения, хранится в стеке и, следовательно, не требует промежуточных выделений памяти. Я профилировал тест, который создает Lookup<int,int> с 100 ключами, каждый из которых имеет 10 000 предметов (всего 1 000 000).

  • Создание Lookup делает 1610 отведений.
  • Копирование его с помощью моего метода делает 1712 выделений (все выделения, необходимые для его создания, плюс одно для каждого делегата в SelectMany вызов и один для перечислителя для каждого ключа).
  • Копирование его с анонимными объектами вместо KeyValuePair выполняет 1 001 712 выделений (все выделения, необходимые для копирования, плюс по одному для каждого элемента).

CPU, даже с 100 000 элементов на ключ в Lookup производительность между двумя методами копирования была одинаковой. С 1 000 000 элементов на ключ производительность была разной для двух методов:

  • 5,1 сек для создания
  • 5,9 сек для копирования KeyValuePair
  • 6,3 сек для копирования с анонимными объектами

Как насчет этого:

return lookup
  .SelectMany (grp => grp, (grp, item) => new { grp.Key, item})
  .ToLookup (x => x.Key, x => x.item);
Другие вопросы по тегам