Существует ли лучший способ агрегировать словарь с помощью LINQ?

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

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

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

(Это действительно работает.)

    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 () или что бы то ни было.

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


У меня была одна реализация..., и другой обнаружился в ответах, в то время как я работал над нею.

Мой:

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

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

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

8
задан Jonathan Mitchem 26 July 2010 в 20:05
поделиться

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()
);
5
ответ дан 5 December 2019 в 20:12
поделиться

Если дублирование ключей является проблемой, возможно, вы имеете в виду 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 ( из ... из ).

3
ответ дан 5 December 2019 в 20:12
поделиться

Вы ищете что-то подобное?

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
ответ дан 5 December 2019 в 20:12
поделиться

Мне нравится ваш метод, потому что он понятен, но если вы хотите сделать его более эффективным, вы можете сделать следующее, которое выполнит всю агрегацию и группировку за один вызов 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;
                                    });
}
0
ответ дан 5 December 2019 в 20:12
поделиться

Если вы обращаетесь к индексатору словаря и там ничего нет, он позволяет вам установить, что он возвращает конструкцию типа данных по умолчанию, в случае двойного значения будет 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;
        });

}

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

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

0
ответ дан 5 December 2019 в 20:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: