ToLookup or not ToLookup, that is the question!
Tags: csharp, dotnet, programming, webdev
Published: 2021-05-30 08:23:00

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()](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tolookup?view=net-5.0). 

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.

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

```console
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](https://mydevtricks.com/linq-gems-indexed-select) for more info about indexed select.)*

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

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

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

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

### Additional Resources

For a thorough deconstruction of the `ToLookup` method, see John Skeet's great article [Reimplementing LINQ to Objects Part 18 – ToLookup](https://codeblog.jonskeet.uk/2010/12/31/reimplementing-linq-to-objects-part-18-tolookup/).

### Conclusion

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 my blog [mydevtricks.com](https://mydevtricks.com).