DEV Community

Jan Doubek

Posted on • Originally published at mydevtricks.com

ToLookup or not ToLookup, that is the question!

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.

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

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

Some Noteworthy Facts

• Similarly to all the other `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.
• The `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.
• If you request a sequence for a key, which is not present inside of the lookup table, you'll get an empty sequence. Again, compare this behavior to Dictionary, which will throw a `KeyNotFoundException` for non-existing keys.
• There is another extension method available, which is almost identical in behavior to what `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!