ToLookup or not ToLookup, that is the question!

I'm an "old-school" Software Engineer, mostly specializing in C# and .NET.
Currently in the process of discovering AI and Machine Learning.
Search for a command to run...

I'm an "old-school" Software Engineer, mostly specializing in C# and .NET.
Currently in the process of discovering AI and Machine Learning.
No comments yet. Be the first to comment.
Since the inception of LINQ, methods suffixed with 'By' have played an important role in simplifying LINQ operations by introducing an additional 'selector' parameter. A typical example is the OrderBy method. Without the selector parameter, even a st...

One key reason I find myself drawn to social media is its power to inspire through the diverse perspectives of people. People tweeting or blogging about stuff they do or like, youtube videos and podcasts on different topics, all of these embed little...

Whether you're a seasoned Microsoft tech stack developer or just starting out, this tutorial is the ideal place to begin your Python journey. And, to sweeten things up a little bit, you're going to use ChatGPT to help you get started! Without further...

In line with the title of this blog, I'd like to start posting articles about languages, tools and technologies that I got a chance to use recently. One of the technologies that I was lucky to learn is PostgreSQL. I have always been an avid Microsoft...

Shakespeare will hopefully forgive me for abusing a line from his famous Hamlet. In my post, I won't be covering questions of life and death, but rather something much more prosaic - a lesser-known LINQ materialization method - ToLookup.
We all know the famous LINQ materialization methods trio - ToArray, ToList, ToDictionary. There's another such method, which, similarly to the other ones, can be quite useful. The other method is Enumerable.ToLookup().
Here's a description of ToLookup from the Microsoft Docs page:
The ToLookup method returns a Lookup, a one-to-many dictionary that maps keys to collections of values.
Ok, so ToLookup is similar to the ToDictionary method. There's one subtle, but important difference though. With Dictionary, which is basically a typed hash table, you are allowed to associate only a single value to each key, i.e. there's always a one-to-one mapping between keys and values. ToLookup on the other hand lets the user store a collection of values for each key, i.e. effectively creating a one-to-many mapping.
That's it for theory. Let's have a look at some examples.
In the first example, we'll use ToLookup to split up a collection of numbers into two collections containing even and odd numbers.
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };
// Using explicit types instead of 'var' for better explicability
ILookup<bool, int> evenNumbersLookup = numbers.ToLookup(num => (num % 2) == 0);
IEnumerable<int> evenNumbers = evenNumbersLookup[true];
IEnumerable<int> oddNumbers = evenNumbersLookup[false];
Console.WriteLine("EVEN: " + string.Join(',', evenNumbers));
Console.WriteLine("ODD: " + string.Join(',', oddNumbers));
Output:
EVEN: 2,4,6,8
ODD: 1,3,5,7
The key of the lookup table here is a boolean determining whether the number is even or odd. The value is a collection of integers satisfying the even/odd condition.
Simple, yet effective.
The lookup key does not necessarily have to be a true/false value - it can be of any type. In the following example, we use ToLookup together with an indexed select to split an input set of numbers into groups of 3 elements.
(Check out one of my previous LINQ Gems articles for more info about indexed select.)
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
ILookup<int, int> groupedNumbersLookup = numbers
.Select((num, seq) => (num, seq))
.ToLookup(key => (key.seq / 3), val => val.num);
var i = 0;
while (groupedNumbersLookup.Contains(i))
{
Console.WriteLine($"GROUP {i+1}: " + string.Join(',', groupedNumbersLookup[i]));
i++;
}
The output will be:
GROUP 1: 1,2,3
GROUP 2: 4,5,6
GROUP 3: 7,8,9
GROUP 4: 10,11,12
And, naturally, the key of the lookup table is not restricted to numbers only. Here is a nice example of a lookup table having a string key:
var carsInStock = new[]
{
new { Make = "Ford", Price = 10000 },
new { Make = "Ford", Price = 15000 },
new { Make = "Ford", Price = 20000 },
new { Make = "Honda", Price = 15000 },
new { Make = "Honda", Price = 25000 },
new { Make = "Jeep", Price = 20000 },
};
var carValues = carsInStock.ToLookup(key => key.Make, val => val.Price);
int fordCarsValue = carValues["Ford"].Sum();
int hondaCarsValue = carValues["Honda"].Sum();
Console.WriteLine($"Total value of all Ford cars in stock: ${ fordCarsValue }");
Console.WriteLine($"Total value of all Honda cars in stock: ${ hondaCarsValue }");
Gives us the output that we'd expect:
Total value of all Ford cars in stock: $45000
Total value of all Honda cars in stock: $40000
ToXXX() methods, ToLookup as well uses immediate execution to populate the returned Lookup table. Any changes to the original sequence (after the method has returned) won’t change the elements stored in the lookup.ILookup<TKey,TElement> interface returned by the ToLookup call is read-only. It is therefore not possible to append or otherwise modify the lookup table. This is in contrast with IDictionary<TKey, TValue> returned by ToDictionary, which constitutes a fully mutable collection of key/value pairs.KeyNotFoundException for non-existing keys.ToLookup does. That method is GroupBy. Really the only difference between these two methods is that GroupBy uses deferred execution, while ToLookup's execution is immediate.For a thorough deconstruction of the ToLookup method, see John Skeet's great article Reimplementing LINQ to Objects Part 18 – ToLookup.
As you can see, the usage of ToLookup is quite versatile. My favorite use case (by far) is the ability to split up a sequence of values into 2 sequences using a single line of code (described in the first example above).
Have you ever used ToLookup in your code? If yes, what was the use case? Share your thoughts in the comments!
If you find this post interesting, be sure to check out other posts on this blog.