Convert list to dictionary using linq and not worrying about duplicates

Viewed 189137

I have a list of Person objects. I want to convert to a Dictionary where the key is the first and last name (concatenated) and the value is the Person object.

The issue is that I have some duplicated people, so this blows up if I use this code:

private Dictionary<string, Person> _people = new Dictionary<string, Person>();

_people = personList.ToDictionary(
    e => e.FirstandLastName,
    StringComparer.OrdinalIgnoreCase);

I know it sounds weird but I don't really care about duplicates names for now. If there are multiple names I just want to grab one. Is there anyway I can write this code above so it just takes one of the names and doesn't blow up on duplicates?

12 Answers

In case we want all the Person (instead of only one Person) in the returning dictionary, we could:

var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));

The issue with most of the other answers is that they use Distinct, GroupBy or ToLookup, which creates an extra Dictionary under the hood. Equally ToUpper creates extra string. This is what I did, which is an almost an exact copy of Microsoft's code except for one change:

    public static Dictionary<TKey, TSource> ToDictionaryIgnoreDup<TSource, TKey>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null) =>
        source.ToDictionaryIgnoreDup(keySelector, i => i, comparer);

    public static Dictionary<TKey, TElement> ToDictionaryIgnoreDup<TSource, TKey, TElement>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
    {
        if (keySelector == null)
            throw new ArgumentNullException(nameof(keySelector));
        if (elementSelector == null)
            throw new ArgumentNullException(nameof(elementSelector));
        var d = new Dictionary<TKey, TElement>(comparer ?? EqualityComparer<TKey>.Default);
        foreach (var element in source)
            d[keySelector(element)] = elementSelector(element);
        return d;
    }

Because a set on the indexer causes it to add the key, it will not throw, and will also do only one key lookup. You can also give it an IEqualityComparer, for example StringComparer.OrdinalIgnoreCase

Using LINQ's equivalent of foldLeft functionality

persons.Aggregate(new Dictionary<string,Person>(StringComparer.OrdinalIgnoreCase),
  (acc, current) => { 
    acc[current.FirstAndLastName] = current; 
    return acc; 
  }); 
Related