Search code examples
c#linqhashmapienumerablelanguage-ext

How to build nested HashMap in C# using LanguageExt in a most-readable way?


In .NET / C# I have input data of type IEnumerable<T> with T having some properties I want to use for lookups.

How can I build a two-level (maybe three-level) lookup using LanguageExt without producing hard to read code like this:

var items = Range(1, 1000)
            .Map(i => new 
                 {
                   Number = i, 
                   Section = (byte) (i % 10), 
                   Text = $"Number is i"
                 }); // just some test data

HashMap<byte, HashMap<int, string>> lookup 
  = toHashMap(
      from item in items
      group item.Text by (item.Section, item.Number) into gInner
      group gInner by gInner.Key.Section into gOuter
      select ( gOuter.Key, toHashMap(gOuter.Map(_ => (_.Key.Number, _.Head()))) )
    );

Expected output: lookup hashmap with Section as outer key, Number as inner key and Text as value.

I prefer solutions using LINQ syntax (maybe making it easier to combine this with transforming / filtering / ordering ...).


Solution

  • Language-ext has built-in extension methods for dealing with nested HashMap and Map types (up to four nested levels deep):

    So, to lookup a type:

    HashMap<int, HashMap<string, DateTime>> lookup = ...;
    
    var value = lookup.Find(123, "Hello");
    

    You can also add:

    lookup = lookup.AddOrUpdate(123, "Hello", value);
    

    There's also Remove, MapRemoveT, MapT, FilterT, FilterRemoveT, Exists, ForAll, SetItemT, TrySetItemT, and FoldT.

    So, to answer your specific question:

    var map = items.Fold(
                HashMap<byte, HashMap<int, string>>(), 
                (s, item) => s.AddOrUpdate(item.Section, item.Number, item.Text));
    

    If you do this a lot, then you could generalise it into an extension of Seq<(A, B, C)>

    public static HashMap<A, HashMap<B, C>> ToHashMap<A, B, C>(this Seq<(A, B, C)> items) =>
        items.Fold(
            HashMap<A, HashMap<B, C>>(), 
            (s, item) => s.AddOrUpdate(item.Item1, item.Item2, item.Item3));
    

    I had the same requirements, that's why I added these to language-ext :)