web-dev-qa-db-fra.com

Convertir une liste en dictionnaire en utilisant linq sans se soucier des doublons

J'ai une liste d'objets Personne. Je veux convertir en dictionnaire où la clé est le nom et le prénom (concaténés) et la valeur est l'objet Personne.

Le problème est que j'ai des personnes en double, alors ça explose si j'utilise ce code:

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

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

Je sais que ça a l'air bizarre mais je ne me soucie pas vraiment des noms en double pour l'instant. S'il y a plusieurs noms, je veux juste en prendre un. Y at-il de toute façon que je puisse écrire ce code ci-dessus pour qu'il prenne simplement l'un des noms et ne explose pas sur les doublons?

124
leora

Voici la solution évidente, non linq:

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

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

Si vous préférez une solution non-LINQ, vous pouvez procéder comme suit:

// 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;
}
360
LukeH

Une solution Linq utilisant Distinct () et sans groupement est:

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

Je ne sais pas si c'est mieux que la solution de LukeH mais ça marche aussi.

37
Tillito

Cela devrait fonctionner avec l'expression lambda:

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

Vous pouvez également utiliser la fonction ToLookup LINQ, que vous pouvez ensuite utiliser de façon presque interchangeable avec un dictionnaire.

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

Cela fera essentiellement GroupBy dans la réponse de LukeH , mais donnera le hachage fourni par un dictionnaire. Donc, vous n'avez probablement pas besoin de le convertir en dictionnaire, mais utilisez simplement la fonction LINQ First chaque fois que vous devez accéder à la valeur de la clé.

9
palswim

Pour gérer l'élimination des doublons, implémentez un IEqualityComparer<Person> qui peut être utilisé dans la méthode Distinct(), puis obtenir votre dictionnaire sera facile ...

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

Obtenez votre dictionnaire:

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

Vous pouvez créer une méthode d'extension similaire à ToDictionary (), à la différence qu'elle autorise les doublons. Quelque chose comme:

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

Dans ce cas, s'il y a des doublons, la dernière valeur l'emporte.

5
Eric
        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);
        }
2
King

Si nous voulons que toute la personne (au lieu d’une seule personne) soit reprise dans le dictionnaire précédent, nous pourrions:

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

À partir de la solution de Carra, vous pouvez également l'écrire comme suit:

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