【问题标题】:Is there a better way to aggregate a dictionary using LINQ?有没有更好的方法来使用 LINQ 聚合字典?
【发布时间】:2010-07-26 19:15:31
【问题描述】:

我正在尝试从可枚举中构建字典,但我需要一个聚合器来存储所有可能重复的键。直接使用 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);
    }

我希望已经有类似的东西了,但我想没有。这将是一个很好的补充。

谢谢大家:-)

【问题讨论】:

  • 您的意思是要使密钥唯一化,还是要删除 dups?
  • 我更新了描述。尝试聚合重复项以使其唯一,然后从中构建字典。

标签: c# linq dictionary group-by aggregate


【解决方案1】:
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()
);

【讨论】:

    【解决方案2】:

    如果重复键是一个问题,也许你的意思是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)。

    【讨论】:

      【解决方案3】:

      我喜欢你的方法,因为它很清楚,但如果你想让它更高效,你可以执行以下操作,这将在单个 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;
                                          });
      }
      

      【讨论】:

      • 不错。有点令人费解......但是是的。我想我不确定我在寻找什么。可能是 ToDictionary() 的重载,它提供了第三个参数来解析重复项?
      【解决方案4】:

      你在寻找这样的东西吗?

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

      【讨论】:

      • 是的,这正是我所拥有的,只是纯粹使用扩展方法语法。
      • 我的不同之处在于它将聚合放入 ToDictionary 调用中,而不是先计算它。
      【解决方案5】:

      如果您访问字典的索引器并且那里没有任何内容,它允许您设置它返回数据类型的默认构造,如果是双精度,它将为 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() 这就是无论如何,它确实在幕后。

      我认为从可读性的角度来看,这可以更容易地说明正在做的事情,除非这不是你想要做的......

      【讨论】:

      • timeEntries[] += 调用中生成KeyNotFoundException: The given key was not present in the dictionary。您需要先初始化字典值,然后才能对其使用 +=。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多