2010-07-26 11 views
8

Estoy intentando construir un diccionario a partir de un enumerable, pero necesito un agregador para todas las claves potencialmente duplicadas. El uso de ToDictionary() directamente causaba ocasionalmente claves duplicadas.¿Existe alguna forma mejor de agregar un diccionario utilizando LINQ?

En este caso, tengo un montón de entradas de tiempo ({DateTime Date, double Hours}), y si se producen varias entradas de tiempo en el mismo día, quiero el tiempo total para ese día. Es decir, un agregador personalizado, que me dará una clave única para una entrada de diccionario.

¿Hay una mejor manera de hacerlo que esta?

(Esto funciona.)

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

creo que realmente estoy buscando algo como esto:

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

así que ...

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

El 'resolutor 'podría ser. Primero() o .Max() o lo que sea.

O algo similar.


Tuve una implementación ... y apareció otra en las respuestas mientras trabajaba en ella.

Mina:

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

Tenía la esperanza de que había algo como eso ya, pero creo que no. Eso sería un buen complemento.

Gracias a todos :-)

+0

¿Quiere decir que desea uniquify la tecla, o quiere quitar los DUP? – Abel

+0

He actualizado la descripción. Intentando agregar los duplicados para hacerlos únicos, y luego construir un diccionario a partir de eso. –

Respuesta

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

llamado por:

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

Si duplicados de las llaves es un problema, tal vez quiere decir ToLookup? Mismo principio, pero los valores múltiples por tecla ...

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

Entonces sólo tiene que hacer algo como:

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 utilizar SelectMany por supuesto (from...from).

0

Si Acess indexador de un diccionario y no hay nada allí, que le permite establecer devuelve una construcción predeterminada del tipo de datos, en el caso de un doble que va a ser 0. Me quizá hacer algo como

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

} 

Lista utilizada recientemente porque, por razones desconocidas, la extensión .ForEach() no está implementada en ienumerable, aunque me imagino que la implementación sería línea por línea idéntica, pero podría hacer un literal foreach() que es lo que hace debajo de las coberturas de todos modos.

Creo que desde el punto de vista de la legibilidad, esto hace que el punto sea mucho más fácil de lo que se está haciendo, a menos que esto no sea lo que estabas tratando de hacer ...

+2

Genera 'KeyNotFoundException: la clave dada no estaba presente en el diccionario' en la llamada' timeEntries [] + = '. Debe inicializar el valor del diccionario antes de poder usar + = en él. –

+0

Ah, sí, Sam, soy tonto, arreglado en edición ahora ... –

0

Me gusta su método porque está claro, pero si desea hacerlo más eficiente puede hacer lo siguiente que hará toda la agregación y agrupación en una sola llamada Aggregate, aunque sea un poco intrincada.

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

Agradable. Un poco intrincado ... pero sí. Supongo que no estoy muy seguro de lo que estoy buscando. ¿Tal vez una sobrecarga para ToDictionary() que proporciona un tercer parámetro para resolver duplicados? –

0

¿Estás buscando algo como esto?

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í, eso es exactamente lo que tengo, solo con la sintaxis del método de extensión. –

+0

La mía es diferente en que pone el agregado en la llamada 'ToDictionary', en lugar de calcularlo primero. – Gabe

+0

Oh, ya veo. Totalmente perdido eso. Genial gracias. –

Cuestiones relacionadas