Конвертировать список в словарь, используя linq и не заботясь о дубликатах


163

У меня есть список объектов Person. Я хочу преобразовать в словарь, где ключ - это имя и фамилия (объединенные), а значение - объект Person.

Проблема в том, что у меня есть несколько дублированных людей, поэтому это взрывается, если я использую этот код:

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

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

Я знаю, это звучит странно, но мне пока не нужны дубликаты имен. Если есть несколько имен, я просто хочу получить одно. В любом случае я могу написать этот код выше, так что он просто берет одно из имен и не взрывается на дубликаты?


1
Дубликаты (основанные на ключе), я не уверен, хотите ли вы сохранить их или потерять их? Хранение их потребует Dictionary<string, List<Person>>(или эквивалент).
— Энтони Пеграм

@ Энтони Пеграм - просто хочу оставить одного из них. я обновил вопрос, чтобы быть более ясным
— лера

ну, вы можете использовать различные, прежде чем делать ToDictionary. но вам придется переопределить методы Equals () и GetHashCode () для класса person, чтобы CLR знал, как сравнивать объекты person
— Sujit.Warrier,

@ Sujit.Warrier - Вы также можете создать средство сравнения на равенствоDistinct
— Кайл Делани

Ответы:


71

Вот очевидное решение, отличное от linq:

foreach(var person in personList)
{
  if(!myDictionary.Keys.Contains(person.FirstAndLastName))
    myDictionary.Add(person.FirstAndLastName, person);
}

208
вот так 2007 :)
— лера

3
это не игнорирует дело
— 2010 года

Да, примерно в то время, когда мы обновляем на платформе .net 2.0 на работе ... @onof Не совсем трудно игнорировать регистр. Просто добавьте все ключи в верхнем регистре.
— Карра

Как бы я сделал этот случай нечувствительным
— Леора

11
Или создайте словарь с StringComparer, который будет игнорировать регистр, если это то, что вам нужно, то ваш код добавления / проверки не заботится, игнорируете ли вы регистр или нет.
— Двоичный беспорядок

423

Решение LINQ:

// Use the first value in group
var _people = personList
    .GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
    .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);

// Use the last value in group
var _people = personList
    .GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
    .ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);

Если вы предпочитаете не-LINQ решение, вы можете сделать что-то вроде этого:

// Use the first value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
    if (!_people.ContainsKey(p.FirstandLastName))
        _people[p.FirstandLastName] = p;
}

// Use the last value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
    _people[p.FirstandLastName] = p;
}

6
@LukeH Небольшое примечание: ваши два фрагмента не эквивалентны: вариант LINQ сохраняет первый элемент, а не-LINQ фрагмент сохраняет последний элемент?
— Toong

4
@toong: Это правда и определенно стоит отметить. (Хотя в этом случае OP, кажется, не волнует, с каким элементом они заканчиваются.)
— LukeH

1
В случае «первого значения»: решение nonLinq дважды выполняет поиск по словарю, но Linq выполняет избыточные экземпляры и итерацию объектов. Оба не идеальны.
— SerG

@SerG К счастью, поиск в словаре обычно считается операцией O (1) и оказывает незначительное влияние.
— Холлис

43

Решение Linq с использованием Distinct () и без группировки:

var _people = personList
    .Select(item => new { Key = item.Key, FirstAndLastName = item.FirstAndLastName })
    .Distinct()
    .ToDictionary(item => item.Key, item => item.FirstFirstAndLastName, StringComparer.OrdinalIgnoreCase);

Я не знаю, является ли это лучше, чем решение LukeH, но оно также работает.


Вы уверены, что это работает? Как Distinct будет сравнивать новый тип ссылки, который вы создаете? Я думаю, вам нужно будет передать какой-то IEqualityComparer в Distinct, чтобы получить эту работу, как задумано.
— Саймон Гилби,

5
Не обращайте внимания на мой предыдущий комментарий. См stackoverflow.com/questions/543482/...
— Simon Gillbee

Если вы хотите переопределить, насколько отчетливо определяется, проверьте stackoverflow.com/questions/489258/…
— Джеймс МакМахон

30

Это должно работать с лямбда-выражением:

personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);

2
Это должно быть:personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
— Gh61

4
Это будет работать, только если IEqualityComparer по умолчанию для класса Person сравнивается по имени и фамилии, игнорируя регистр. В противном случае напишите такой IEqualityComparer и используйте соответствующую перегрузку Distinct. Также ваш метод ToDIctionary должен использовать регистр без учета регистра, чтобы соответствовать требованию OP.
— Джо

13

Вы также можете использовать ToLookupфункцию LINQ, которую затем можно использовать почти взаимозаменяемо со словарем.

_people = personList
    .ToLookup(e => e.FirstandLastName, StringComparer.OrdinalIgnoreCase);
_people.ToDictionary(kl => kl.Key, kl => kl.First()); // Potentially unnecessary

Это, по сути, сделает GroupBy в ответе LukeH , но даст хеширование, которое обеспечивает словарь. Таким образом, вам, вероятно, не нужно преобразовывать его в словарь, а просто использовать Firstфункцию LINQ всякий раз, когда вам нужно получить доступ к значению ключа.


8

Вы можете создать метод расширения, аналогичный ToDictionary (), с той разницей, что он допускает дублирование. Что-то вроде:

    public static Dictionary<TKey, TElement> SafeToDictionary<TSource, TKey, TElement>(
        this IEnumerable<TSource> source, 
        Func<TSource, TKey> keySelector, 
        Func<TSource, TElement> elementSelector, 
        IEqualityComparer<TKey> comparer = null)
    {
        var dictionary = new Dictionary<TKey, TElement>(comparer);

        if (source == null)
        {
            return dictionary;
        }

        foreach (TSource element in source)
        {
            dictionary[keySelector(element)] = elementSelector(element);
        }

        return dictionary; 
    }

В этом случае, если есть дубликаты, выигрывает последнее значение.


7

Чтобы обработать устранение дубликатов, внедрите метод, IEqualityComparer<Person>который можно использовать в Distinct()методе, и тогда получить ваш словарь будет легко. Дано:

class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.FirstAndLastName.Equals(y.FirstAndLastName, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(Person obj)
    {
        return obj.FirstAndLastName.ToUpper().GetHashCode();
    }
}

class Person
{
    public string FirstAndLastName { get; set; }
}

Получите ваш словарь:

List<Person> people = new List<Person>()
{
    new Person() { FirstAndLastName = "Bob Sanders" },
    new Person() { FirstAndLastName = "Bob Sanders" },
    new Person() { FirstAndLastName = "Jane Thomas" }
};

Dictionary<string, Person> dictionary =
    people.Distinct(new PersonComparer()).ToDictionary(p => p.FirstAndLastName, p => p);

2
        DataTable DT = new DataTable();
        DT.Columns.Add("first", typeof(string));
        DT.Columns.Add("second", typeof(string));

        DT.Rows.Add("ss", "test1");
        DT.Rows.Add("sss", "test2");
        DT.Rows.Add("sys", "test3");
        DT.Rows.Add("ss", "test4");
        DT.Rows.Add("ss", "test5");
        DT.Rows.Add("sts", "test6");

        var dr = DT.AsEnumerable().GroupBy(S => S.Field<string>("first")).Select(S => S.First()).
            Select(S => new KeyValuePair<string, string>(S.Field<string>("first"), S.Field<string>("second"))).
           ToDictionary(S => S.Key, T => T.Value);

        foreach (var item in dr)
        {
            Console.WriteLine(item.Key + "-" + item.Value);
        }

Я предлагаю вам улучшить свой пример, прочитав Минимальный, Полный и проверяемый пример .
— IlGala

2

В случае, если мы хотим, чтобы весь Person (а не только один Person) в возвращаемом словаре, мы могли бы:

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

1
Извините, игнорируйте мой отзыв-редактирование (я не могу найти, где удалить мой обзор-редактирование). Я просто хотел добавить предложение об использовании g.First () вместо g.Select (x => x).
— Алекс 75

1

Проблема с большинством других ответов является то , что они используют Distinct, GroupByили ToLookup, что создает дополнительный словарь под капотом. Также ToUpper создает дополнительную строку. Это то, что я сделал, это почти точная копия кода Microsoft, за исключением одного изменения:

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

Поскольку набор в индексаторе заставляет его добавлять ключ, он не будет выбрасывать, а также будет выполнять только один поиск ключа. Вы также можете дать ему IEqualityComparer, например,StringComparer.OrdinalIgnoreCase


0

Начиная с решения Карры, вы также можете написать его так:

foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
    myDictionary.Add(person.FirstAndLastName, person);
}

3
Не то, чтобы кто-то когда-либо пытался использовать это, но не пытайтесь использовать это. Модификация коллекций во время их итерации - плохая идея.
— kidmosey
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.