Monday, January 27, 2014

Introducing NOtherLookup - even better lookups

Recently I've been discussing how awesome is the ILookup data structure provided by .NET Framework as a part of LINQ library. I've looked through some of its key features, but I've also mentioned its few imperfections, like no way to create it directly and no easy way to do any operations on a Lookup as a whole, promising to take a look how to fix it.

So, how to fix it? By using NOtherLookup library I've just pushed to NuGet. It is a set of extension methods and utility classes designed to make Lookup-based operations as easy and as natural as in plain LINQ-to-objects.

Consider for example merging two Lookup instances into a single one that has the keys from both instances and for each key that is present in both Lookups, the union of sets should be produced. When doing it in plain LINQ, we probably need to convert both Lookups to IDictionary<TKey, List<TValue>>, then merge corresponding keys and finally produce a new Lookup manually. With NOtherLookup, we can just use LINQ in the same way as we do on IEnumerable:

var result = firstLookup.Union(secondLookup)

In this example, result is still typed as ILookup<TKey, TValue>, with no more conversions needed. And here is what it does:

firstLookup:
"a" => 1,2
"b" => 3,4

secondLookup:
"b" => 4,5
"c" => 6,7

result:
"a" => 1,2
"b" => 3,4,5
"c" => 6,7

NOtherLookup offers support for several operators known from LINQ. There are some operators that combine two Lookups - besides Union shown above, there is Concat, Except, Intersect, Join and Zip. There are some that manipulate a single Lookup - Select and Where. There is also quite universal OnEachKey method that allow running arbitrary LINQ query on each lookup element, maintaining ILookup type.

ILookup<int, string> transformed = lookup.OnEachKey(g => g.Select(x => x + g.Key).Reverse());

Example:

lookup:
"a" => 1,2
"b" => 3,4

transformed:
"a" => "2a", "1a"
"b" => "4b", "3b"

NOtherLookup contains also a few features that make obtaining ILookups easier. The most important one is Lookup.Builder - a class that allows creating lookup from the scratch (as a solution for the lack of Lookup public constructor), with support for custom comparators, if needed.

ILookup<int, string> lookup = Lookup.Builder
    .WithKey(1, new[] { "a", "b" })
    .WithKey(2, new[] { "c", "d" }) 
    .WithComparer(new CustomIntComparer()) // can be omitted
    .Build();

There is also empty lookup available for your convenience.

Last but not least, there are easy conversions between ILookup and IDictionary available - it's a simple method call in both ways:

ILookup<int, string> lookup = dict.ToLookup();
Dictionary<int, List<string>> backToDict = lookup.ToDictionary();

For the detailed descriptions with examples, see README on GitHub project page, where the code lives. NOtherLookup is licenced with very liberal MIT licence, so feel free to grab the code and use it any way you want.

1 comment: