Linq: создать пустую IGrouping - PullRequest
3 голосов
/ 08 ноября 2011

Я хотел бы создать функцию с использованием Linq, которая суммирует входящую последовательность значений. Функция должна выглядеть примерно так:

IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values)
{
    return values
        .ToLookup(val => GetKey(val))         // group values by key
        .Union(*an empty grouping*)           // make sure there is a default group
        .ToDictionary(
            group => group.Key,
            group => CreateSummary(group));   // summarize each group
}

Подвох в том, что результирующий IDictionary должен иметь запись по умолчанию (TKey), даже если входящая последовательность не содержит значений с этим ключом. Можно ли это сделать чисто функциональным способом? (Без использования изменяемых структур данных.)

Единственный способ, которым я могу думать, это вызвать .Union при поиске, прежде чем отправлять его в словарь. Но это потребовало бы от меня создания пустой IGrouping, что, по-видимому, невозможно без явного класса. Есть ли элегантный способ сделать это?

Редактировать: мы можем предположить, что TKey является типом значения.

Ответы [ 3 ]

3 голосов
/ 08 ноября 2011

Вы не можете получить пустые группы ни из GroupBy, ни из ToLookup. Возможно, есть преднамеренная причина.

Можно ли это сделать чисто функциональным способом? (Без использования изменяемых структур данных.)

Хотя такие академические требования могут быть забавными, любое решение следует сравнивать с простотой простой реализации.

Dictionary<TKey, Summary<TKey>> result = values
  .GroupBy(val => GetKey(val))
  .ToDictionary(g => g.Key, g => CreateSummary(g));

TKey x = default(TKey);
if (!result.ContainsKey(x))
{
  result[x] = CreateSummary(Enumerable.Empty<TValue>());
}

return result;

Теперь, если вы хотите пустую группу, просто добавьте класс для нее:

public class EmptyGroup<TKey, TValue> : IGrouping<TKey, TValue>
{
  public TKey Key {get;set;}

  public IEnumerator GetEnumerator()
  {
    return GetEnumerator<TValue>();
  }
  public IEnumerator<TValue> GetEnumerator<TValue>()
  {
    return Enumerable.Empty<TValue>().GetEnumerator<TValue>();
  }
}

Используется так:

EmptyGroup<TKey, TValue> empty = new EmptyGroup<TKey, TValue>(Key = default<TKey>());
2 голосов
/ 15 января 2014

Принятый ответ - это то, что я искал, но у меня это не сработало.Может быть, я что-то пропустил, но это не скомпилировано.Мне пришлось изменить код, чтобы исправить это.Вот код, который работал для меня:

public class EmptyGroup<TKey, TValue> : IGrouping<TKey, TValue>
{
    public TKey Key { get; set; }

    public IEnumerator<TValue> GetEnumerator()
    {
        return Enumerable.Empty<TValue>().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

используется следующим образом

var emptyGroup = new EmptyGroup<Customer, AccountingPaymentClient>();
0 голосов
/ 08 ноября 2011

Вы можете добавить второй выбор, в котором вы проверяете, есть ли в таблице поиска записи, и если нет, создайте новую таблицу поиска.Это отличается от предложенного решения объединения, так как не добавляет значения по умолчанию, ЕСЛИ есть другие значения.

см .:

IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values) 
{ 
    return values 
        .ToLookup(val => GetKey(val))         // group values by key 
        .Select(x => x.Any() ? x : Enumerable.Repeat(default(TKey), 1).ToLookup(x => GetKey(x)))
        .ToDictionary( 
            group => group.Key, 
            group => CreateSummary(group));   // summarize each group 
} 

Если вы хотите решение с объединением,вы можете использовать ту же логику для создания таблицы поиска по умолчанию, например:

IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values) 
{ 
    return values 
        .ToLookup(val => GetKey(val))         // group values by key 
        .Union(Enumerable.Repeat(default(TKey), 1).ToLookup(x => GetKey(x)))
        .ToDictionary( 
            group => group.Key, 
            group => CreateSummary(group));   // summarize each group 
} 

Надеюсь, это поможет

...