Home > Net >  Convert List<type> to Dictionary<tkey, tvalue>
Convert List<type> to Dictionary<tkey, tvalue>

Time:06-25

I have a list of type MyType and I want to group them by their key and only keep the last updated element. Here's what I have tried:

var myObjectsList = new List<MyType> {.....};

var myDictionary = new Dictionary<Guid, MyType>();
foreach (var myObject in myObjectsList)
{
    if (myObject is null || (myObject.key ?? Guid.Empty) == Guid.Empty)
        continue;

    var key = myObject.key ?? Guid.Empty;
    if (!myDictionary.ContainsKey(key))
    {
        myDictionary[key] = myObject;
        continue;
    }

    var storedVehicle = myDictionary[key];
    if (storedVehicle.LasatUpdate < myObject.LasatUpdate)
        myDictionary[key] = myObject;
}

I am wondering if there is a cleaner way to this operation using Enumerable.ToDictionary like it is explained here. What I found so far is the following:

var groupedDictionary = myObjectsList
                             .GroupBy(x => x.key)
                             .ToDictionary(gdc => gdc.key, gdc => gdc.ToList());

which group them by key and store all in a list rather than only keeping the last-updated item.

CodePudding user response:

You can use this overload of GroupBy to create a IEnumerable<KeyValuePair<Guid, MyType>>, then use that to create a dictionary.

new Dictionary<Guid, MyType>(
    myObjectsList
    .Where(x => x.Key is not null && x.Key != Guid.Empty)
    .GroupBy(
        x => x.Key, // group by the guid
        // for each group, find the object with the latest update 
        (key, objects) => new KeyValuePair<Guid, MyType>(key.Value, objects.MaxBy(y => y.LastUpdate))
    )
);

Note that MaxBy requires .NET 6. A simple but slow alternative on lower versions is to OrderByDescending and then First().

CodePudding user response:

As you saw, there's an existing ToDictionary() method. It looks something like this:

public static Dictionary<TKey, TValue> ToDictionary(this IEnumerable<TValue> items, Func<TValue,TKey> keySelector)
{
    Dictionary<TKey, TValue> result = new();
    foreach(var item in items)
    {
        var key = keySelector(item);
        if (result.ContainsKey(key)) throw new ArgumentException($"Key {key} already exists.");
        result[key] = item;
    }
    return result;
}

But as you also noticed, it won't work for you because it throws an exception if a key already exists, instead of keeping the prior element or updating with the new.

But now that we've peeked behind the scenes to see how it works, you can also write your own version that won't do that. It won't take much... all we have to do is remove the line to throw the exception, like this:

public static Dictionary<TKey, TValue> ToDictionaryEx(this IEnumerable<TValue> items, Func<TValue,TKey> keySelector)
{
    Dictionary<TKey, TValue> result = new();
    foreach(var item in items)
    {
        result[keySelector(item)] = item;
    }
    return result;
}

One nice feature is you don't even need to use GroupBy() to call it. You can just let it run over the full enumerable, and the natural result will be for later items with a given key to replace the earlier items, such that the desired last item will be there at the end:

var groupedDictionary = myObjectsList.ToDictionaryEx(gdc => gdc.key);
  • Related