繁体   English   中英

从 IEnumerable 创建一个 Lookup&lt;,&gt; <igrouping<,> &gt; </igrouping<,>

[英]Create a Lookup<,> from a IEnumerable<IGrouping<,>>

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

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

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<,>这样我们就可以访问它的有用信息索引器
第一部分可以轻松完成:

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

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


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

除了 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. 想到
  2. 经过考虑的
  3. 设计的
  4. 实施的
  5. 经过测试
  6. 记录在案
  7. 支持的

并且要么a)它没有到达#1,要么b)它被认为是,但被抛出或推迟到#2和#7之间的某个地方,因为要么c)它被积极地认为是一个坏主意,要么d) 这是一个足够好的想法,但与好主意的海洋相比,它没有达到收益与努力的必要门槛,以获得时间去做。

我不清楚为什么Lookup<TKey, TValue> class 会公开。 这个 class 没有公共构造函数,似乎没有返回这个具体类型的公共 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();

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

如果效率是问题,我不确定您为什么不直接查找 go :

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

然后,您可以在使用查找时忽略小于 3 的条目。如果您要经常执行它,请创建一个方法,本地 function 或 class 来封装逻辑。 您还提到了 memory,但除非您摆脱名称并仅保留查找,否则它有点没有意义 - 查找不包含所有名称的克隆/您不会通过索引这些您赢得的项目来额外燃烧大量'最终不会使用。 如果您追求真正有效的(速度和内存)解决方案,请不要使用 LiNQ

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM