Linq:创建空的IGrouping

5
我想使用Linq创建一个汇总传入值的函数。该函数大致应如下所示:
IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values)
{
    return values
        .ToLookup(val => GetKey(val))         // group values by key
        .Union(*an empty grouping*)           // make sure there is a default group
        .ToDictionary(
            group => group.Key,
            group => CreateSummary(group));   // summarize each group
}

捕捉是:即使传入的序列中不包含具有该键的值,生成的IDictionary也应该有一个default(TKey)的条目。这能通过纯函数式方法实现吗?(不使用可变数据结构。)
我唯一想到的方法是,在将其导入字典之前在查找上调用.Union。但那需要我创建一个空IGrouping,而没有明确的类似IGrouping的类型。有一种优雅的方式来解决这个问题吗?
编辑:我们可以假设TKey是值类型。
3个回答

5

我需要的是被采纳的答案,但是它对我没用。可能是我漏了什么,但是它没有编译成功。我不得不修改代码以修复它。这里是对我有用的代码:

public class EmptyGroup<TKey, TValue> : IGrouping<TKey, TValue>
{
    public TKey Key { get; set; }

    public IEnumerator<TValue> GetEnumerator()
    {
        return Enumerable.Empty<TValue>().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

使用方法如下

var emptyGroup = new EmptyGroup<Customer, AccountingPaymentClient>();

谢谢!这非常适合单元测试,以覆盖从空组调用“Sum”的代码分支。 - GuiRitter

4

GroupBy和ToLookup都无法返回空组。这可能是有意为之的。

这能否以纯函数式的方式完成?(不使用可变数据结构。)

虽然这样的学术要求可能很有趣,但任何解决方案都应该与直观实现的简单性进行比较。

Dictionary<TKey, Summary<TKey>> result = values
  .GroupBy(val => GetKey(val))
  .ToDictionary(g => g.Key, g => CreateSummary(g));

TKey x = default(TKey);
if (!result.ContainsKey(x))
{
  result[x] = CreateSummary(Enumerable.Empty<TValue>());
}

return result;

现在如果你想要一个空的分组,只需要给它添加一个类:

public class EmptyGroup<TKey, TValue> : IGrouping<TKey, TValue>
{
  public TKey Key {get;set;}

  public IEnumerator GetEnumerator()
  {
    return GetEnumerator<TValue>();
  }
  public IEnumerator<TValue> GetEnumerator<TValue>()
  {
    return Enumerable.Empty<TValue>().GetEnumerator<TValue>();
  }
}

使用方法如下:

EmptyGroup<TKey, TValue> empty = new EmptyGroup<TKey, TValue>(Key = default<TKey>());

2

你可以添加第二个选择器,检查查找表中是否有任何条目,如果没有,则创建一个新的查找表。这与您提出的联合解决方案不同,因为它不会在存在其他值时添加默认值。

参见:

IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values) 
{ 
    return values 
        .ToLookup(val => GetKey(val))         // group values by key 
        .Select(x => x.Any() ? x : Enumerable.Repeat(default(TKey), 1).ToLookup(x => GetKey(x)))
        .ToDictionary( 
            group => group.Key, 
            group => CreateSummary(group));   // summarize each group 
} 

如果您想使用联合查询来解决问题,可以使用相同的逻辑来创建一个默认的查找表,例如:
IDictionary<TKey, Summary<TKey>> Summarize<TKey, TValue>(IEnumerable<TValue> values) 
{ 
    return values 
        .ToLookup(val => GetKey(val))         // group values by key 
        .Union(Enumerable.Repeat(default(TKey), 1).ToLookup(x => GetKey(x)))
        .ToDictionary( 
            group => group.Key, 
            group => CreateSummary(group));   // summarize each group 
} 

希望这能帮到您。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接