tisdag 5 september 2017

"ToLookup" to the rescue when "GroupBy" or "ToDictionary" isn't quite right for the job

More than once I've been writing code using LINQs GroupBy and ToDictionary methods with a feeling that the code didn't become as clear as I wanted it to be. I did some web searching, hoping that there might exist a third party library with a smart solution, but what I found though, was that I didn't even have to install another library, LINQ already contained a method for it!

I'll try to show you how ToLookup can be useful with a phony example.


Suppose a group of smurfs take turn rolling a die and want us to print the result on the screen, like this:
 Smurfs that rolled a 1  
  - Grouchy Smurf  
  - Greedy Smurf  
 Smurfs that rolled a 2  
  - Smurfette  
 Smurfs that rolled a 3  
  - Papa Smurf  
  - Hefty Smurf  
 Smurfs that rolled a 4  
 Smurfs that rolled a 5  
  - Brainy Smurf  
 Smurfs that rolled a 6  
  - Clumsy Smurf  

For this I use a class to store each result in:
 class DieRollResult  
   public string Player { get; set; }  
   public int Value { get; set; }  

And a list to store all the results of a round in:
 var resultList = new List<DieRollResult>()  
   new DieRollResult{ Player= "Papa Smurf", Value = 3},  
   new DieRollResult{ Player= "Smurfette", Value = 2},  
   new DieRollResult{ Player= "Hefty Smurf", Value = 3},  
   new DieRollResult{ Player= "Brainy Smurf", Value = 5},  
   new DieRollResult{ Player= "Grouchy Smurf", Value = 1},  
   new DieRollResult{ Player= "Clumsy Smurf", Value = 6},  
   new DieRollResult{ Player= "Greedy Smurf", Value = 1},  

A solution using GroupBy

Notice the usage of SingleOrDefault to find the right group, it isn't possible to find it by using an index. 
Notice also that using this result set no smurf rolled a 4, and because empty groups like this are allowed a null check has to be done before iterating through the group to avoid a NullReferenceException.

A solution using GroupBy combined with ToDictionary

This time, notice the rather verbose expression to assign the variable resultGroups.
Notice also that this time, the group can be fetched using an index, but we still have to check if the dictionary really contains that index key to avoid a KeyNotFoundException.

A solution using ToLookup

So, finally, by using the ToLookup method, the assignment of the variable resultGroups is as short and clear as in the GroupBy example.
It is also possible to use an index to get the right group to iterate through. And since the ILookup object just returns an empty IEnumerable when it is indexed with a key that it doesn't contain, there is no need to check for null or if the key exists like we had to do in the other examples!

This is why I like the ToLookup method, it raises the signal-to-noise ratio a tiny bit, but sometimes it is just that small improvement that can make the code clearer. 

On the downside though, the method seems to be used so seldom that I think few programmers have seen it, so using it might make the code break the "Principle of least surprise".

Inga kommentarer:

Skicka en kommentar