从 IEnumerable<IGrouping<,>>

时间:2021-03-20 18:12:34

标签: c# linq

我不确定这个问题是否适合 StackOverflow。如果是这样,请告诉我。

我正在尝试从 IEnumerable<IGrouping<,>> 创建一个 Lookup<,>,只是为了它(这不是 XY 问题)。
我的理解是,创建 Lookup 对象的唯一方法是使用 ToLookup 方法。
我发现这样做的最佳方法是将分组分成具有重复键的键值对,然后使用 Lookup 将其再次分组到 ToLookup 中:

groups // IEnumerable<IGrouping<TKey, TElement>>
    .SelectMany(group => group.Select(item => new KeyValuePair<TKey, TElement>(group.Key, item)))
    .ToLookup(kvp => kvp.Key, kvp => kvp.Value)

我认为这是非常低效的,因为它将组分开然后“重新组合”它们,而不是利用它们已经分组的事实。
有没有更好的方法来做到这一点?


可能的用例:
假设我们有一个名字列表。我们想按名字的第一个字母对名字进行分组,到目前为止很好,但我们只想保留有两个以上名字的组,我们希望结果是 Lookup<,>,这样我们就可以访问它的有用的indexer
第一部分可以轻松完成:

names.GroupBy(name => name[0]).Where(group => group.Count() > 2)

但是我们需要将 IEnumerable<IGrouping<char, string>> 转换为 Lookup<char, string>


没有等效于 Dictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>>) 的构造函数的原因是什么?

4 个答案:

答案 0 :(得分:2)

除了 Marc 指出的可以解释为什么此类功能不可用的可能原因之外,我只想补充一点,Dictionary 中也提供了索引器,因此您可以创建一个 IDictionary<char, IEnumerable<string>>,然后保留请记住,如果您将索引器与不在字典中的键一起使用,您将得到一个异常(这是与 ILookup... 中的索引器的一个重要区别...字典)。

所以你可以这样做:

using System;
using System.Linq;
using System.Collections.Generic;

                    
public class Program
{
    public static void Main()
    {
        var names = new List<string>();
        
        names.Add("Agustin");   
        names.Add("Alejandro"); 
        names.Add("Diego"); 
        names.Add("Damian");
        names.Add("Dario");
        
        IDictionary<char, IEnumerable<string>> fakeLookup = names.GroupBy(name => name[0])
            .Where(group => group.Count() > 2)
            .ToDictionary(group => group.Key, group => group.AsEnumerable());
        
        foreach(var name in fakeLookup ['D'])
        {
            Console.WriteLine(name);
        }

        var namesStartingWithA = lookup['A']; // This will throw a KeyNotFoundException

    }
}

答案 1 :(得分:1)

“没有等效于...的构造函数的原因是什么” - 因为每个功能都需要:

  1. 想到
  2. 考虑过
  3. 设计
  4. 已实施
  5. 经过测试
  6. 记录
  7. 支持

或者a)它没有达到#1,或者b)它被考虑过,但是在#2和#7之间被抛弃或推迟,因为c)它被积极地认为是一个坏的想法,或者 d) 这是一个足够好的想法,但是与 海洋 的好想法相比,它没有达到必要的收益与努力的门槛来获得时间去做.

答案 2 :(得分:1)

我不清楚为什么 Lookup<TKey, TValue> 类会被公开。这个类没有公共构造函数,似乎也没有返回这个具体类型的公共API。 ToLookup LINQ 运算符返回接口 (ILookup<TKey, TValue>) 而不是此类型。

如果您想有效地将​​ IEnumerable<IGrouping<TKey, TValue>> 转换为 ILookup<TKey, TValue>,而无需从头开始重建分组,除了编写此接口的自定义实现之外,似乎别无选择。实现不需要公开,而且非常简单:

private class LookupOfGroupings<TKey, TValue> : ILookup<TKey, TValue>
{
    private readonly Dictionary<TKey, IGrouping<TKey, TValue>> _dictionary;

    public LookupOfGroupings(IEnumerable<IGrouping<TKey, TValue>> source) =>
        _dictionary = source.ToDictionary(g => g.Key);

    public int Count => _dictionary.Count;

    public IEnumerable<TValue> this[TKey key]
        => _dictionary.TryGetValue(key, out var g) ? g : Enumerable.Empty<TValue>();

    public bool Contains(TKey key) => _dictionary.ContainsKey(key);

    public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
        => _dictionary.Values.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

索引器的行为与本机实现的行为相同。如果键不存在,则返回一个空序列。

这是执行转换的自定义 ToLookup 运算符:

public static ILookup<TKey, TValue> ToLookup<TKey, TValue>(
    this IEnumerable<IGrouping<TKey, TValue>> source)
        => new LookupOfGroupings<TKey, TValue>(source);

用法示例:

ILookup<char, string> lookup = names
    .GroupBy(name => name[0])
    .Where(group => group.Count() > 2)
    .ToLookup();

答案 3 :(得分:0)

<块引用>

我发现最好的方法是将分组分成具有重复键的键值对,然后再次分组

如果考虑的是效率,我不太确定您为什么不直接查找:

var look = names.ToLookup(n=> n[0], n => n);

然后您可以在使用查找时忽略小于 3 的条目。如果您将执行很多操作,请创建一个方法、本地函数或类来封装逻辑。您还提到了内存,但除非您删除名称并仅保留查找,否则它有点没有实际意义 - 查找不包含所有名称的克隆/您不会通过索引这些您赢得的项目来消耗大量额外费用最终没有使用。如果您追求真正高效(速度和内存)的解决方案,请不要使用 LiNQ

相关问题