Grouper par dans LINQ


1063

Supposons que si nous avons une classe comme:

class Person { 
    internal int PersonID; 
    internal string car; 
}

Maintenant, j'ai une liste de cette classe: List<Person> persons;

Maintenant, cette liste peut avoir plusieurs instances avec les mêmes PersonID, par exemple:

persons[0] = new Person { PersonID = 1, car = "Ferrari" }; 
persons[1] = new Person { PersonID = 1, car = "BMW"     }; 
persons[2] = new Person { PersonID = 2, car = "Audi"    }; 

Existe-t-il un moyen de me regrouper PersonIDet d'obtenir la liste de toutes les voitures qu'il a?

Par exemple, le résultat attendu serait

class Result { 
   int PersonID;
   List<string> cars; 
}

Donc après le regroupement, j'obtiendrais:

results[0].PersonID = 1; 
List<string> cars = results[0].cars; 

result[1].PersonID = 2; 
List<string> cars = result[1].cars;

D'après ce que j'ai fait jusqu'à présent:

var results = from p in persons
              group p by p.PersonID into g
              select new { PersonID = g.Key, // this is where I am not sure what to do

Quelqu'un pourrait-il m'orienter dans la bonne direction?


1
Il y a un autre exemple incluant Countet Sumici stackoverflow.com/questions/3414080/…
Développeur

@Martin Källman: Je suis d'accord avec Chris Walsh. Très probablement, une application qui a la classe (table) "personne (s)" du PO aurait déjà une classe (table) "" normale "" "personne (s)" qui a l'usu. Propriétés / colonnes (nom, sexe, date de naissance). La classe «personne (s)» (tableau) du PO serait uniquement une classe enfant (table) de la classe «personne» («normal») (tableau) (c.-à-d. Une classe «article (s) de commande» (tableau)). ) par rapport à une classe "Ordre (s)" (tableau)). Le PO n'utilisait probablement pas le nom réel qu'il utiliserait s'il était dans le même champ d'application que sa classe ("Tableau") "" normale "" "Personne (s)" et / ou aurait pu le simplifier pour ce poste.
Tom

Réponses:


1735

Absolument - vous voulez essentiellement:

var results = from p in persons
              group p.car by p.PersonId into g
              select new { PersonId = g.Key, Cars = g.ToList() };

Ou en tant qu'expression sans requête:

var results = persons.GroupBy(
    p => p.PersonId, 
    p => p.car,
    (key, g) => new { PersonId = key, Cars = g.ToList() });

Fondamentalement, le contenu du groupe (lorsqu'il est considéré comme un IEnumerable<T>) est une séquence de toutes les valeurs présentes dans la projection ( p.cardans ce cas) présente pour la clé donnée.

Pour en savoir plus sur le GroupByfonctionnement, consultez mon article Edulinq sur le sujet .

(Je l' ai retitré PersonIDà PersonIddans ce qui précède, de suivre les conventions de nommage .NET .)

Alternativement, vous pouvez utiliser Lookup:

var carsByPersonId = persons.ToLookup(p => p.PersonId, p => p.car);

Vous pouvez alors obtenir les voitures pour chaque personne très facilement:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];

11
@jon Skeet et si je veux ajouter une autre propriété comme name
user123456

22
@Mohammad: Ensuite, vous incluez cela dans le type anonyme.
Jon Skeet

7
@ user123456 voici une bonne explication du regroupement par, il comprend également un exemple de regroupement par une clé composite: Comment: Group Query Results (Guide de programmation C #)
Mathieu Diepman

14
@Mohammad, vous pouvez faire quelque chose comme .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})et vous obtiendrez toutes les personnes qui se produisent avec un identifiant, un nom et un certain nombre d'occurrences pour chaque personne.
Chris

11
@kame: Je suivais délibérément les conventions de dénomination .NET, en fixant les noms de l'OP, en gros. Cela sera clair dans la réponse.
Jon Skeet

52
var results = from p in persons
              group p by p.PersonID into g
              select new { PersonID = g.Key,
                           /**/car = g.Select(g=>g.car).FirstOrDefault()/**/}

37
var results = from p in persons
              group p by p.PersonID into g
              select new { PersonID = g.Key, Cars = g.Select(m => m.car) };

32

Vous pouvez également essayer ceci:

var results= persons.GroupBy(n => new { n.PersonId, n.car})
                .Select(g => new {
                               g.Key.PersonId,
                               g.Key.car)}).ToList();

C'est faux retourne la même liste ne se regroupe pas
Vikas Gupta

C'est du travail, je pense qu'il manque quelque chose c'est pourquoi ça ne marche pas dans votre code.
shuvo sarker

29

essayer

persons.GroupBy(x => x.PersonId).Select(x => x)

ou

pour vérifier si une personne se répète dans votre liste, essayez

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)

13

J'ai créé un exemple de code de travail avec la syntaxe de requête et la syntaxe de méthode. J'espère que ça aide les autres :)

Vous pouvez également exécuter le code sur .Net Fiddle ici:

using System;
using System.Linq;
using System.Collections.Generic;

class Person
{ 
    public int PersonId; 
    public string car  ; 
}

class Result
{ 
   public int PersonId;
   public List<string> Cars; 
}

public class Program
{
    public static void Main()
    {
        List<Person> persons = new List<Person>()
        {
            new Person { PersonId = 1, car = "Ferrari" },
            new Person { PersonId = 1, car = "BMW" },
            new Person { PersonId = 2, car = "Audi"}
        };

        //With Query Syntax

        List<Result> results1 = (
            from p in persons
            group p by p.PersonId into g
            select new Result()
                {
                    PersonId = g.Key, 
                    Cars = g.Select(c => c.car).ToList()
                }
            ).ToList();

        foreach (Result item in results1)
        {
            Console.WriteLine(item.PersonId);
            foreach(string car in item.Cars)
            {
                Console.WriteLine(car);
            }
        }

        Console.WriteLine("-----------");

        //Method Syntax

        List<Result> results2 = persons
            .GroupBy(p => p.PersonId, 
                     (k, c) => new Result()
                             {
                                 PersonId = k,
                                 Cars = c.Select(cs => cs.car).ToList()
                             }
                    ).ToList();

        foreach (Result item in results2)
        {
            Console.WriteLine(item.PersonId);
            foreach(string car in item.Cars)
            {
                Console.WriteLine(car);
            }
        }
    }
}

Voici le résultat:

1
Ferrari
BMW
2
Audi
-----------
1
Ferrari
BMW
2
Audi


Veuillez expliquer ce que fait votre code pour le plaisir. Ceci est juste une réponse de code qui est presque une mauvaise réponse.
V.7

2

Essaye ça :

var results= persons.GroupBy(n => n.PersonId)
            .Select(g => new {
                           PersonId=g.Key,
                           Cars=g.Select(p=>p.car).ToList())}).ToList();

Mais en termes de performances, la pratique suivante est meilleure et plus optimisée dans l'utilisation de la mémoire (lorsque notre tableau contient beaucoup plus d'éléments comme des millions):

var carDic=new Dictionary<int,List<string>>();
for(int i=0;i<persons.length;i++)
{
   var person=persons[i];
   if(carDic.ContainsKey(person.PersonId))
   {
        carDic[person.PersonId].Add(person.car);
   }
   else
   {
        carDic[person.PersonId]=new List<string>(){person.car};
   }
}
//returns the list of cars for PersonId 1
var carList=carDic[1];

4
g.Key.PersonId? g.SelectMany?? Vous n'avez clairement pas essayé cela.
Gert Arnold

vous écrivez j'ai édité quelques codes codes et je ne l'ai pas testé. Mon point principal était la deuxième partie. Mais de toute façon merci pour votre considération. Il était trop tard pour éditer ce code quand j'ai réalisé que c'était faux. donc g.Key remplace g.Key.PersonId, et Select plutôt que SelectMany! désordre désolé :)))
akazemis

2
@akazemis: J'essayais en fait de créer (pour utiliser des termes équivalents au domaine OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Le plus proche que j'ai pu obtenir en utilisant LINQ était IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. J'ai fini par utiliser votre forméthode de boucle qui, au fait, était 2x plus rapide. Aussi, j'utiliserais: a) foreachvs foret b) TryGetValuevs ContainsKey(les deux pour le principe DRY - en code et en runtime).
Tom

1

Une autre façon de procéder pourrait être de sélectionner un PersonIdgroupe distinct et de se joindre à persons:

var result = 
    from id in persons.Select(x => x.PersonId).Distinct()
    join p2 in persons on id equals p2.PersonId into gr // apply group join here
    select new 
    {
        PersonId = id,
        Cars = gr.Select(x => x.Car).ToList(),
    };

Ou la même chose avec une syntaxe API fluide:

var result = persons.Select(x => x.PersonId).Distinct()
    .GroupJoin(persons, id => id, p => p.PersonId, (id, gr) => new
    {
        PersonId = id,
        Cars = gr.Select(x => x.Car).ToList(),
    });

GroupJoin produit une liste d'entrées dans la première liste (liste de PersonIddans notre cas), chacune avec un groupe d'entrées jointes dans la deuxième liste (liste de persons).


1

L'exemple suivant utilise la méthode GroupBy pour renvoyer des objets regroupés par PersonID.

var results = persons.GroupBy(x => x.PersonID)
              .Select(x => (PersonID: x.Key, Cars: x.Select(p => p.car).ToList())
              ).ToList();

Ou

 var results = persons.GroupBy(
               person => person.PersonID,
               (key, groupPerson) => (PersonID: key, Cars: groupPerson.Select(x => x.car).ToList()));

Ou

 var results = from person in persons
               group person by person.PersonID into groupPerson
               select (PersonID: groupPerson.Key, Cars: groupPerson.Select(x => x.car).ToList());

Ou vous pouvez utiliser ToLookup, ToLookuputilise essentiellement EqualityComparer<TKey>.Default pour comparer les clés et faire ce que vous devez faire manuellement lorsque vous utilisez le regroupement par et dans le dictionnaire. je pense que c'est inmemory excuted

 ILookup<int, string> results = persons.ToLookup(
            person => person.PersonID,
            person => person.car);

0

Tout d'abord, définissez votre champ clé. Incluez ensuite vos autres champs:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()

La réponse / le code non commenté n'est pas la façon dont StackOverflow fonctionne ...
V.7
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.