Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a Lookup<,> from a IEnumerable<IGrouping<,>>

Tags:

c#

linq

I'm not sure this question fits in 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).
My understanding is that the only way to create a Lookup object is with the ToLookup method.
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:

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.
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>.


What reasons there are for not having a constructor equivalent to Dictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>>)?

like image 566
Roy Cohen Avatar asked Sep 18 '25 06:09

Roy Cohen


1 Answers

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).

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

    }
}
like image 147
dglozano Avatar answered Sep 20 '25 19:09

dglozano