Raggruppa in LINQ


1063

Supponiamo che abbiamo una classe come:

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

Ora ho un elenco di questa classe: List<Person> persons;

Ora questo elenco può avere più istanze con le stesse PersonID, ad esempio:

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

C'è un modo in cui posso raggruppare PersonIDe ottenere l'elenco di tutte le auto che ha?

Ad esempio, il risultato atteso sarebbe

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

Quindi dopo il raggruppamento, otterrei:

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

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

Da quello che ho fatto finora:

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

Qualcuno potrebbe per favore indirizzarmi nella giusta direzione?


1
C'è un altro esempio incluso Counte Sumqui stackoverflow.com/questions/3414080/…
Sviluppatore

@Martin Källman: sono d'accordo con Chris Walsh. Molto probabilmente un'app che ha la classe (tabella) "Persona (e)" del PO avrebbe già una classe (tabella) "normale" "" Persona (e) "che ha l'usu. Proprietà / colonne (es. Nome, genere, DOB). La classe (tabella) "Persona (e)" del PO sarebbe in gran parte una classe (tabella) figlio della classe "tabella" (")" (normale) "persona (e)" (ovvero una classe "ordine / i") ) rispetto a una classe "Ordine / i" (tabella)). L'OP probabilmente non stava usando il nome effettivo che avrebbe usato se fosse nello stesso ambito della sua classe (tabella) "normale" "persona (e)" e / o potrebbe averlo semplificato per questo post.
Tom,

Risposte:


1735

Assolutamente - sostanzialmente vuoi:

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

O come espressione senza query:

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

Fondamentalmente il contenuto del gruppo (se visto come un IEnumerable<T>) è una sequenza di qualunque valore fosse presente nella proiezione ( p.carin questo caso) presente per la chiave data.

Per ulteriori informazioni su come GroupByfunziona, vedere il mio post su Edulinq sull'argomento .

(Ho rinominato PersonIDa PersonIdin quanto sopra, a seguire NET convenzioni di denominazione .)

In alternativa, è possibile utilizzare un Lookup:

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

È quindi possibile ottenere le auto per ogni persona molto facilmente:

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

11
@jon Skeet cosa succede se voglio aggiungere un'altra proprietà come il nome
user123456

22
@Mohammad: poi lo includi nel tipo anonimo.
Jon Skeet,

7
@ user123456 ecco una buona spiegazione del raggruppamento per, include anche un esempio di raggruppamento per chiave composita: Procedura: Raggruppare i risultati delle query (Guida per programmatori C #)
Mathieu Diepman

14
@Mohammad puoi fare qualcosa del genere .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})e otterrai tutte le persone che si presentano con un ID, un nome e un numero di occorrenze per ogni persona.
Chris,

11
@kame: stavo seguendo deliberatamente le convenzioni di denominazione .NET, fondamentalmente fissando i nomi degli OP. Lo chiarirà nella risposta.
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

Puoi anche provare questo:

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

È sbagliato restituisce lo stesso elenco non raggruppato per
Vikas Gupta,

Funziona, penso che manchi qualcosa ed è per questo che non funziona nel tuo codice.
shuvo sarker,

29

provare

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

o

per verificare se qualcuno sta ripetendo nella tua lista prova

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

13

Ho creato un esempio di codice funzionante con Sintassi query e Sintassi metodo. Spero che aiuti gli altri :)

Puoi anche eseguire il codice su .Net Fiddle qui:

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

Ecco il risultato:

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


Per favore, spiega cosa fa il tuo codice per amor. Questa è solo una risposta in codice che è quasi una risposta sbagliata.
V.7,

2

Prova questo :

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

Ma per quanto riguarda le prestazioni, la seguente pratica è migliore e più ottimizzata nell'uso della memoria (quando il nostro array contiene molti più elementi come milioni):

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?? Chiaramente non hai provato questo.
Gert Arnold,

stai scrivendo ho modificato alcuni codici in esso e non l'ho provato. Il mio punto principale era la seconda parte. Ma comunque grazie per la tua considerazione. Era troppo tardi per modificare quel codice quando ho capito che era sbagliato. quindi g.Key sostituisce g.Key.PersonId e Select anziché SelectMany! così disordinato scusa :)))
Akazemis,

2
@akazemis: stavo davvero cercando di creare (per usare termini equivalenti al dominio di OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Il più vicino che potevo ottenere usando LINQ era IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Ho finito con il tuo formetodo loop che, tra l'altro, era 2 volte più veloce. Inoltre, utilizzerei: a) foreachvs. foreb) TryGetValuevs. ContainsKey(entrambi per il principio DRY - in codice e runtime).
Tom,

1

Un modo alternativo per farlo potrebbe essere quello di selezionare distinti PersonIde unire il gruppo con 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(),
    };

O lo stesso con la sintassi API fluente:

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 produce un elenco di voci nel primo elenco (elenco PersonIdnel nostro caso), ognuna con un gruppo di voci unite nel secondo elenco (elenco di persons).


1

L'esempio seguente utilizza il metodo GroupBy per restituire oggetti raggruppati per PersonID.

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

O

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

O

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

Oppure puoi usare ToLookup, sostanzialmente ToLookupusa EqualityComparer<TKey>.Default per confrontare le chiavi e fare ciò che dovresti fare manualmente quando usi group by e nel dizionario. penso che sia scampato alla memoria

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

0

Innanzitutto, imposta il campo chiave. Quindi includere gli altri campi:

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

La risposta / codice non commentato non è il modo in cui StackOverflow funziona ...
V.7,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.