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);