2010-07-26 5 views
8

Я пытаюсь создать словарь из перечислимого, но мне нужен агрегатор для всех возможных дубликатов ключей. Использование ToDictionary() напрямую вызывало повторяющиеся ключи.Есть ли лучший способ для объединения словаря с помощью LINQ?

В этом случае у меня есть куча записей времени ({DateTime Date, double Hours}), и если в тот же день происходит несколько записей времени, я хочу общее время для этого дня. I.e., настраиваемый агрегатор, который даст мне уникальный ключ для записи словаря.

Есть ли лучший способ сделать это, чем это?

(Это работает.)

private static Dictionary<DateTime, double> CreateAggregatedDictionaryByDate(IEnumerable<TimeEntry> timeEntries) 
    { 
     return 
      timeEntries 
       .GroupBy(te => new {te.Date}) 
       .Select(group => new {group.Key.Date, Hours = group.Select(te => te.Hours).Sum()}) 
       .ToDictionary(te => te.Date, te => te.Hours); 
    } 

Я думаю, что я действительно искал что-то вроде этого:

IEnumerable<T>.ToDictionary( 
    /* key selector : T -> TKey */, 
    /* value selector : T -> TValue */, 
    /* duplicate resolver : IEnumerable<TValue> -> TValue */); 

так ...

timeEntries.ToDictionary( 
    te => te.Date, 
    te => te.Hours, 
    duplicates => duplicates.Sum()); 

«распознаватель 'может быть .First() или .Max() или что-то еще.

Или что-то подобное.


У меня была одна реализация ... и еще одна из них появилась в ответах, когда я работал над этим.

Mine:

public static Dictionary<TKey, TValue> ToDictionary<T, TKey, TValue>(
     this IEnumerable<T> input, 
     Func<T, TKey> keySelector, 
     Func<T, TValue> valueSelector, 
     Func<IEnumerable<TValue>, TValue> duplicateResolver) 
    { 
     return input 
      .GroupBy(keySelector) 
      .Select(group => new { group.Key, Value = duplicateResolver(group.Select(valueSelector)) }) 
      .ToDictionary(k => k.Key, k => k.Value); 
    } 

Я надеялся, что там было что-то подобное уже, но я не думаю. Это было бы приятным дополнением.

Спасибо всем :-)

+0

ли вы имеете в виду, что вы хотите uniquify ключ, или вы хотите, чтобы удалить Dups? – Abel

+0

Я обновил описание. Попытка объединить дубликаты, чтобы сделать их уникальными, а затем построить словарь из этого. –

ответ

5
public static Dictionary<KeyType, ValueType> ToDictionary 
    <SourceType, KeyType, ValueType> 
(
    this IEnumerable<SourceType> source, 
    Func<SourceType, KeyType> KeySelector, 
    Func<SourceType, ValueType> ValueSelector, 
    Func<IGrouping<KeyType, ValueType>, ValueType> GroupHandler 
) 
{ 
    Dictionary<KeyType, ValueType> result = source 
    .GroupBy(KeySelector, ValueSelector) 
    .ToDictionary(g => g.Key, GroupHandler); 
} 

Вызывается:

Dictionary<DateTime, double> result = timeEntries.ToDictionary(
    te => te.Date, 
    te => te.Hours, 
    g => g.Sum() 
); 
3

Если дубликаты ключей является проблемой, может быть, вы имеете в виду ToLookup? Тот же основной, но несколько значений для каждого ключа ...

private static ILookup<DateTime, double> CreateAggregatedDictionaryByDate(IEnumerable<TimeEntry> timeEntries) 
{ 
    return 
     timeEntries 
      .GroupBy(te => new {te.Date}) 
      .Select(group => new {group.Key.Date, Hours = group.Select(te => te.Hours).Sum()}) 
      .ToLookup(te => te.Date, te => te.Hours); 
} 

Тогда вы просто сделать что-то вроде:

var lookup = CreateAggregatedDictionaryByDate(...); 
foreach(var grp in lookup) { 
    Console.WriteLine(grp.Key); // the DateTime 
    foreach(var hours in grp) { // the set of doubles per Key 
     Console.WriteLine(hours) 
    } 
} 

или использовать SelectMany конечно (from...from).

0

Если вы Асесс индексатора словаря и там ничего нет, это позволяет установить его возвращает строительство по умолчанию типа данных, в случае двойного это будет 0. Я бы, возможно, сделать что-то вроде

public void blabla(List<TimeEntry> hoho) 
{ 
    Dictionary<DateTime, double> timeEntries = new Dictionary<DateTime, double>(); 
    hoho.ForEach((timeEntry) => 
     { 
      timeEntries[timeEntry.Day] = 0; 
     }); 

    hoho.ForEach((timeEntry) => 
     { 
      timeEntries[timeEntry.Day] += timeEntry.Hours; 
     }); 

} 

Только что использованный Список, поскольку по неизвестным причинам расширение .ForEach() не реализовано в ienumerable, хотя я бы предположил, что реализация будет линией для строки идентичной, но вы можете просто сделать литерал foreach(), который что он делает под обложками в любом случае.

Я думаю, что с точки зрения читаемости это становится намного легче того, что делается, если это не то, что вы пытались сделать.

+2

Производит 'KeyNotFoundException: данный ключ не присутствовал в словаре' в' timeEntries [] + = 'вызов. Вам нужно инициализировать значение словаря, прежде чем вы сможете использовать + = на нем. –

+0

Ах, правый Сэм, глупый меня, исправлен в редакции сейчас. –

0

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

private static Dictionary<DateTime, double> CreateAggregatedDictionaryByDate(IEnumerable<TimeEntry> timeEntries) 
{ 
    return timeEntries.Aggregate(new Dictionary<DateTime, double>(), 
           (accumulator, entry) => 
            { 
             double value; 
             accumulator.TryGetValue(entry.Date, out value); 
             accumulator[entry.Date] = value + entry.Hours; 
             return accumulator; 
            }); 
} 
+1

Ницца. Немного запутанный ... но да. Думаю, я не совсем уверен, что я ищу. Может быть, перегрузка для ToDictionary(), которая обеспечивает третий параметр для устранения дубликатов? –

0

Вы ищете что-то в этом роде?

private static Dictionary<DateTime, double> CreateAggregatedDictionaryByDate(IEnumerable<TimeEntry> timeEntries) 
{ 
    return 
     (from te in timeEntries 
     group te by te.Date into grp) 
     .ToDictionary(grp => grp.Key, (from te in grp select te.Hours).Sum()); 
} 
+0

Да, это именно то, что у меня есть, просто с синтаксисом метода расширения. –

+0

Mine отличается тем, что он помещает агрегат в вызов «ToDictionary», а не вычисляет его в первую очередь. – Gabe

+0

О, я вижу. Полностью пропустил это. Приятно, спасибо. –

Смежные вопросы