Let's have a phone book:
Notice that John and Irene share the same phone number.
We want to get an inverse phone book, mapping numbers to groups of names:
Now, I wrote the following generic method in C# to produce an "inverse" dictionary:
private static IDictionary<T, IList<U>> ToInverseDictionary<T, U>(IDictionary<U, T> dictionary)
{
var inverseDictionary = new Dictionary<T, IList<U>>();
foreach (var objU in dictionary.Keys)
{
var objT = dictionary[objU];
if (inverseDictionary.TryGetValue(objT, out var objsUExisting))
{
objsUExisting.Add(objU);
}
else
{
var objsU = new List<U>();
objsU.Add(objU);
inverseDictionary[objT] = objsU;
}
}
return inverseDictionary;
}
Then I was advised to use LINQ. So I came up with this:
private static IDictionary<T, IList<U>> ToInverseDictionary<T, U>(IDictionary<U, T> dictionary)
{
return dictionary.GroupBy(kvp => kvp.Value).ToDictionary(grouping => grouping.Key, grouping => grouping.Select(g => g.Key).ToList());
}
My question is: Does the latter code snippet produce the same results as the former one?
I'm not quite sure because there are "nested lamdas" and I'm a little bit lost in the code.
Let's do some quick formatting of your second method to help explain what this code does.
(the only code change is that the value of the resulting dictionary is a List
not IList
).
private static IDictionary<T, List<U>> ToInverseDictionary<T, U>(IDictionary<U, T> dictionary)
{
return dictionary
.GroupBy(kvp => kvp.Value)
.ToDictionary(
grouping => grouping.Key,
grouping => grouping
.Select(g => g.Key)
.ToList()
);
}
Taking this one line at a time:
dictionary
- this is our input object as <U, T>
. Ultimately we're looking for a dictionary of type <T, List<U>>
.
.GroupBy(kvp => kvp.Value)
- Imagine you have a bunch of colored marbles. This method moves them around by some property, we'd want the color of each marble. That results in a collection of groups, Each group has two attributes: the key (what you grouped on: the color), and the list of marbles in said group.
In this code, you're inspecting each KeyValuePair (kvp
) in the input dictionary and grouping by the Value (the phone number). This means you'll end up with 4 grouping, because that's the number of distinct phone numbers you start with. Each grouping contains KeyValuePair entries it is composed of - which is going to be either 1 or 2 entries in your case.
If we stop here and look at the result of .GroupBy
we would see something like this:
Group Key | Group Values |
---|---|
6463 | KeyValuePair<string, int>("John", 6463) , KeyValuePair<string, int>("Irene", 6463) |
1234 | KeyValuePair<string, int>("Andrea", 1234) |
2611 | KeyValuePair<string, int>("Michael", 2611) |
7699 | KeyValuePair<string, int>("Matthew", 7699) |
This is already starting to look a lot like what you want, but it's just not a Dictionary
yet.
.ToDictionary(
- it's time to build the result dictionary. The method takes two parameters, a function to define the Key
, and a function to define the Value
, for each object in the source collection.
grouping => grouping.Key,
- grouping
is, as you'd expect, representing each group made in .GroupBy()
. grouping.Key
is a T
type and how each group is determined (=> kvp.Value
in .GroupBy
).
grouping => grouping
- to determine the Value
of the resulting Dictionary, we start by looking at the list of all entries in the group...
.Select(g => g.Key)
- for each entry in that group, take the Key
of the KeyValuePair (not to be confused with the Key property of an IGrouping
). The Key property of the KeyValuePair means Keys of the original, input dictionary, which are the people's names. You end up with a collection of just the people's names that are in the grouping
group.
.ToList()
- .Select()
will produce an IEnumerable<string>
, we want to make this a List.
You end up a List<string>
, which is all the people's names that are in that grouping.
If I had to speculate, I would say most of the confusion here lies in the fact that you're using KeyValuePair
, IGrouping
, and Dictionary
all at the same time, which have Key
and Value
properties, making it easy to confuse which object type you're thinking something belongs to.