Конвертировать список в словарь, используя 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;
}
LukeH
источник
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, но оно также работает.

Tillito
источник
Вы уверены, что это работает? Как 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 всякий раз, когда вам нужно получить доступ к значению ключа.

palswim
источник
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);
}
Cinquo
источник
3
Не то, чтобы кто-то когда-либо пытался использовать это, но не пытайтесь использовать это. Модификация коллекций во время их итерации - плохая идея.
kidmosey