简体   繁体   English

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

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

I'm not sure this question fits in StackOverflow.我不确定这个问题是否适合 StackOverflow。 If that's the case, please let me know.如果是这样,请告诉我。

I'm trying to create a Lookup<,> from an IEnumerable<IGrouping<,>> , just for the sake of it (this isn't an XY problem).我正在尝试从IEnumerable<IGrouping<,>>创建一个Lookup<,> ,只是为了它(这不是 XY 问题)。
My understanding is that the only way to create a Lookup object is with the ToLookup method.我的理解是创建Lookup object 的唯一方法是使用ToLookup方法。
The best way I found to do this is to separate the groupings into key-value pairs with duplicate keys and then group it again into a Lookup using 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)

I think this is very inefficient because it separates the groups and then 'reassembles' them, instead of taking advantage of the fact that they're already grouped.我认为这是非常低效的,因为它将组分开然后“重新组装”它们,而不是利用它们已经分组的事实。
Is there a better way to do this?有一个更好的方法吗?


Possible use case:可能的用例:
Let's say we have a list of names.假设我们有一个名称列表。 We want to group the names by their first letter, so far so good, but we only want to keep groups with more than two names, and we want the result to be a Lookup<,> so we'll have access to its useful indexer .我们希望按名称的第一个字母对名称进行分组,到目前为止一切都很好,但我们只想保留具有两个以上名称的组,并且我们希望结果是Lookup<,>这样我们就可以访问它的有用信息索引器
The first part can be done easily:第一部分可以轻松完成:

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

But then we will need to convert the IEnumerable<IGrouping<char, string>> to a Lookup<char, string> .但是我们需要将IEnumerable<IGrouping<char, string>>转换为Lookup<char, string>


What reasons there are for not having a constructor equivalent to Dictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>>) ?没有与Dictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>>)等效的构造函数的原因是什么?

In addition to the possible reasons that could explain why such functionality is not available that Marc pointed out, I just wanted to add that the indexer is also available in Dictionary, so you could create a IDictionary<char, IEnumerable<string>> and then keep in mind that you will get an Exception if you use the indexer with a key that's not in the dictionary (which is an important difference with the indexer in the ILookup ... in addition to the Lookup being immutable in contrast to the dictionary).除了 Marc 指出的可以解释为什么此类功能不可用的可能原因之外,我只想补充一点,索引器在 Dictionary 中也可用,因此您可以创建一个IDictionary<char, IEnumerable<string>>然后请记住,如果您将索引器与不在字典中的键一起使用,则会出现异常(这是与ILookup中的索引器的重要区别......除了查找与字典相比是不可变的) .

So you could do something like this:所以你可以做这样的事情:

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

    }
}

"What reasons there are for not having a constructor equivalent to..." - because every feature needs to be: “没有等效于...的构造函数的原因是什么”-因为每个功能都需要:

  1. thought of想到
  2. considered经过考虑的
  3. designed设计的
  4. implemented实施的
  5. tested经过测试
  6. documented记录在案
  7. supported支持的

and either a) it didn't get to #1, or b) it was thought of, but got thrown out or deferred somewhere between #2 and #7, because either c) it was actively thought to be a bad idea, or d) it was a good-enough idea, but when compared to the sea of good ideas, it didn't meet the necessary threshold of benefit vs effort to get given the time to do it.并且要么a)它没有到达#1,要么b)它被认为是,但被抛出或推迟到#2和#7之间的某个地方,因为要么c)它被积极地认为是一个坏主意,要么d) 这是一个足够好的想法,但与好主意的海洋相比,它没有达到收益与努力的必要门槛,以获得时间去做。

It is unclear to me why the Lookup<TKey, TValue> class is publicly exposed.我不清楚为什么Lookup<TKey, TValue> class 会公开。 This class has no public constructors, and it seems that there is no public API that returns this concrete type.这个 class 没有公共构造函数,似乎没有返回这个具体类型的公共 API。 The ToLookup LINQ operator returns an interface ( ILookup<TKey, TValue> ) instead of this type. ToLookup LINQ 运算符返回一个接口 ( ILookup<TKey, TValue> ) 而不是这种类型。

If you want to convert efficiently an IEnumerable<IGrouping<TKey, TValue>> to an ILookup<TKey, TValue> , without reconstructing the groupings from scratch, there seems to be no other option than writing a custom implementation of this interface.如果您想有效地将IEnumerable<IGrouping<TKey, TValue>>转换为ILookup<TKey, TValue> ,而不从头开始重建分组,似乎没有其他选择,只能编写此接口的自定义实现。 The implementation does not need to be public, and it's quite straightforward:实现不需要公开,而且非常简单:

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

The behavior of the indexer is the same with the behavior of the native implementation.索引器的行为与本机实现的行为相同。 In case of a non-existent key, it returns an empty sequence.如果键不存在,则返回一个空序列。

And here is the custom ToLookup operator that performs the conversion:这是执行转换的自定义ToLookup运算符:

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

Usage example:使用示例:

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

The best way I found to do this is to separate the groupings into key-value pairs with duplicate keys and then group it again我发现这样做的最好方法是将分组分成具有重复键的键值对,然后再次分组

If efficiency is the concern, I'm not really sure why you didn't just go straight to a lookup:如果效率是问题,我不确定您为什么不直接查找 go :

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

Then you can ignore the entries that have less than 3 as you use the lookup.. If you'll be performing it a lot, make a method, local function or class to encapsulate the logic.然后,您可以在使用查找时忽略小于 3 的条目。如果您要经常执行它,请创建一个方法,本地 function 或 class 来封装逻辑。 You also mentioned about memory, but it's somewhat moot unless you get rid of names and just retain the lookup - the lookup doesn't contain a clone of all the names/you're not burning a huge amount extra by indexing these items you won't end up using.您还提到了 memory,但除非您摆脱名称并仅保留查找,否则它有点没有意义 - 查找不包含所有名称的克隆/您不会通过索引这些您赢得的项目来额外燃烧大量'最终不会使用。 If you're after a truly efficient (for speed and memory) solution, don't use LiNQ如果您追求真正有效的(速度和内存)解决方案,请不要使用 LiNQ

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

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