Anna
Anna

Reputation:

LINQ Convert Dictionary to Lookup

I have a variable of type Dictionary<MyType, List<MyOtherType>>
I want to convert it to a Lookup<MyType, MyOtherType>.

I wanted to use Lambda functions to first, flatten the dictionary and then convert this to Lookup using the ToLookup(). I got stuck with the dictionary. I thought about using SelectMany but can't get it working. Anyone has got an idea how to do it?

Upvotes: 24

Views: 6354

Answers (5)

Michael Fry
Michael Fry

Reputation: 1090

Late to the party but I think this should work, without needing to enumerate everything again and create temporary tuples/anonymous types.

public static ILookup<TKey, TElement> ToLookup<TKey, TElement>(
    this IEnumerable<TKey> keys,
    Func<TKey, IEnumerable<TElement>> selector)
{
    return new ManualLookup<TKey, TElement>(keys, selector);
}

private class ManualLookup<TKey, TElement> : ILookup<TKey, TElement>
{
    private IEnumerable<TKey> _keys;
    private Func<TKey, IEnumerable<TElement>> _selector;

    public ManualLookup(IEnumerable<TKey> keys, Func<TKey, IEnumerable<TElement>> selector)
    {
        _keys = keys;
        _selector = selector;
    }

    public IEnumerable<TElement> this[TKey key] => _selector(key);

    public int Count => _keys.Count();

    public bool Contains(TKey key) => _keys.Contains(key);

    public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator() => _keys
        .Select(key => new ManualGrouping<TKey, TElement>(key, _selector(key)))
        .GetEnumerator();

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

private class ManualGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    private TKey _key;
    private IEnumerable<TElement> _enumerable;

    public ManualGrouping(TKey key, IEnumerable<TElement> enumerable)
    {
        _key = key;
        _enumerable = enumerable;
    }

    public TKey Key => _key;

    public IEnumerator<TElement> GetEnumerator() => _enumerable.GetEnumerator();

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

Then you can do something like:

Dictionary<MyType, List<MyOtherType>> dictionary;
return dictionary.Keys.ToLookup(key => 
{
    if (dictionary.TryGetValue(key, out var list)
    {
        return list;
    }

    return Enumerable.Empty<MyOtherType>();
});

Upvotes: 0

resnyanskiy
resnyanskiy

Reputation: 1637

Not an answer for the question, but I think this is related information and should be posted here.

There is some edge cases you should take into account. All of them about items of dictionary, which have key, but don't have value.

This is expected behavior. Dictionary and Lookup designed for different purposes.

var dic = new Dictionary<bool, IEnumerable<bool?>> { [true] = null };
var lookup = dic.ToLookup();

Assert.AreEqual(1, dic.Count);
Assert.AreEqual(0, lookup.Count);

Assert.IsTrue(dic.ContainsKey(true));
Assert.IsFalse(lookup.Contains(true));

Assert.IsFalse(dic.ContainsKey(false));
Assert.IsFalse(lookup.Contains(false));

dic[false] -> Exception
lookup[false] -> bool?[0]

Upvotes: 0

Dan
Dan

Reputation: 13343

Already a few answers here, but putting this here for reference. This flips a dictionary with a list of values, to having those values as the keys of look up list.

var myLookup = myDict.SelectMany(p => p.Value, 
        (pair, id) => Tuple.Create(id, pair.Key))
    .ToLookup(p => p.Item1, p => p.Item2);

Annotated


var myLookup = myDict.SelectMany(
        // specify that the select many is to be based off the Value which is a list of items
        p => p.Value, 
        // Using the individual items from that list, create a tuple of that item and the dictionary key it was under
        (pair, id) => Tuple.Create(id, pair.Key))
        // use the item as the lookup key, and put the original dictionary key (that
        // had that value underneath them) in the list of lookup values.
    .ToLookup(p => p.Item1, p => p.Item2);

Upvotes: 1

3dGrabber
3dGrabber

Reputation: 5074

Same as Jon's method, but avoiding the creation of an anonymous type:

var lookup = dictionary
            .SelectMany(p => p.Value, Tuple.Create)
            .ToLookup(p => p.Item1.Key, p => p.Item2);

Upvotes: 28

Jon Skeet
Jon Skeet

Reputation: 1500485

How about:

var lookup = dictionary.SelectMany(pair => pair.Value,
                                   (pair, Value) => new { pair.Key, Value })
                       .ToLookup(pair => pair.Key, pair => pair.Value);

It does feel like a little bit of a waste doing this when the dictionary already has all the information grouped appropriately, but I can't see a simple way round that. Of course you could implement ILookup<TKey, TValue> yourself with a wrapper around the dictionary...

Upvotes: 19

Related Questions