2010-07-26 5 views
8

Sto cercando di creare un dizionario da una enumerabile, ma ho bisogno di un aggregatore per tutte le chiavi potenzialmente duplicate. L'uso diretto di ToDictionary() causava occasionalmente chiavi duplicate.C'è un modo migliore per aggregare un dizionario usando LINQ?

In questo caso, ho un certo numero di voci di tempo ({DateTime Date, double Hours}), e se più voci temporali si verificano nello stesso giorno, voglio il tempo totale per quel giorno. Ad esempio, un aggregatore personalizzato, che mi darà una chiave univoca per una voce del dizionario.

C'è un modo migliore per farlo rispetto a questo?

(Questo funziona.)

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); 
    } 

Penso che sto davvero cercando qualcosa di simile:

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

così ...

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

Il 'resolver 'potrebbe essere .First() o .Max() o qualsiasi altra cosa.

O qualcosa di simile.


Ho avuto un'implementazione ... e un'altra è comparsa nelle risposte mentre ci stavo lavorando.

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); 
    } 

Speravo ci fosse qualcosa di simile già, ma io non credo. Sarebbe una bella aggiunta.

Grazie a tutti :-)

+0

Vuoi dire che si desidera uniquify la chiave, o si desidera rimuovere i dups? – Abel

+0

Ho aggiornato la descrizione. Cercando di aggregare i duplicati per renderli unici, e quindi creare un dizionario da questo. –

risposta

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); 
} 

chiamato da:

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

Se chiavi duplicate è un problema, forse vuoi dire ToLookup? Stesso principio, ma più valori per tasto ...

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); 
} 

Quindi è sufficiente fare qualcosa di simile:

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) 
    } 
} 

o utilizzare SelectMany ovviamente (from...from).

0

Se acess indicizzatore di un dizionario e non c'è niente, permette di impostare restituisce una costruzione di default del tipo di dati, nel caso di un doppio sarà 0. avrei forse fare qualcosa di simile

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; 
     }); 

} 

Elenco appena utilizzato perché per motivi sconosciuti, l'estensione .ForEach() non è implementata su ienumerable, anche se immagino che l'implementazione sia riga per riga identica, ma si può semplicemente fare un foreach letterale() che è comunque cosa fa sotto le coperte

Penso che dal punto di vista della leggibilità, questo sia il punto attraverso molto più facile di ciò che viene fatto, a meno che non sia quello che stavi cercando di fare ..

+2

Genera 'KeyNotFoundException: la chiave specificata non era presente nel dizionario' sulla chiamata' timeEntries [] + = '. È necessario inizializzare il valore del dizionario prima di poter utilizzare + = su di esso. –

+0

Ah, giusto Sam, stupido, fisso in modifica ora .. –

0

Mi piace il tuo metodo perché è chiaro, ma vuoi renderlo più efficiente puoi fare quanto segue che farà tutte le aggregazioni e raggruppamenti in una singola chiamata Aggregate, anche se leggermente contorta.

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

Bello. Un po 'contorto ... ma si. Immagino di non essere veramente sicuro di cosa sto cercando. Forse un sovraccarico per ToDictionary() che fornisce un terzo parametro per risolvere i duplicati? –

0

Stai cercando qualcosa di simile?

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

Sì, è esattamente quello che ho, solo con la sintassi del metodo di estensione. –

+0

Il mio è diverso in quanto inserisce l'aggregazione nella chiamata "ToDictionary", anziché calcolarla prima. – Gabe

+0

Oh, capisco. Assolutamente mancato. Bene grazie. –