有没有更好的方法来使用LINQ聚合字典?

时间:2010-07-26 19:15:32

标签: c# linq aggregate group-by todictionary

我正在尝试从可枚举的构建字典,但我需要一个聚合器来处理所有可能重复的键。直接使用ToDictionary()偶尔会导致重复键。

在这种情况下,我有一堆时间条目({DateTime Date,double Hours}),如果同一天有多个时间条目,我想要那天的总时间。即,一个自定义聚合器,它将为我提供一个字典条目的唯一键。

有没有比这更好的方法呢?

(这确实有效。)

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

我希望已经有类似的东西,但我猜不是。那将是一个很好的补充。

谢谢大家: - )

5 个答案:

答案 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()
);

答案 1 :(得分: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)
    }
}

或当然使用SelectManyfrom...from)。

答案 2 :(得分:0)

如果您访问字典的索引器并且没有任何内容,它允许您设置它返回数据类型的默认构造,在double的情况下它将为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因为原因不明,。onEach()扩展没有在ienumerable上实现,即使我想象实现的行是行相同的,但你可以只做一个文字foreach()这是什么无论如何它都在封面下。

我认为从可读性的角度来看,除了这不是你想要做的事情之外,这使得所做的事情变得更容易了。

答案 3 :(得分:0)

我喜欢你的方法'因为它很清楚,但是你想要提高它的效率,你可以做以下事情,它将在一次Aggregate调用中进行所有聚合和分组,虽然是一个稍微复杂的。< / p>

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

答案 4 :(得分: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());
}