Agrupar por en LINQ

1063

Supongamos que tenemos una clase como:

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

Ahora tengo una lista de esta clase: List<Person> persons;

Ahora esta lista puede tener múltiples instancias con las mismas PersonID, por ejemplo:

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

¿Hay alguna forma de agruparme PersonIDy obtener la lista de todos los autos que tiene?

Por ejemplo, el resultado esperado sería

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

Entonces, después de agrupar, obtendría:

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

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

Por lo que he hecho hasta ahora:

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

¿Podría alguien señalarme en la dirección correcta?

test123
fuente
1
Hay otro ejemplo que incluye County Sumaquí stackoverflow.com/questions/3414080/…
Desarrollador el
@ Martin Källman: Estoy de acuerdo con Chris Walsh. Lo más probable es que una aplicación que tenga la Clase (Tabla) "Persona (s)" del OP ya tenga una Clase (Tabla) "'normal'" "Persona (s)" que tenga el usu. Propiedades / columnas (es decir, nombre, género, fecha de nacimiento). La clase (tabla) "Persona (s)" del OP sería una clase (tabla) secundaria de la clase (tabla) "persona (s)" "normal" (es decir, una clase (tabla) "elemento (s) de pedido" ) frente a una clase "Orden (es)" (Tabla)). El OP probablemente no estaba usando el nombre real que usaría si estuviera en el mismo Alcance que su Clase (Tabla) "'normal'" "Persona (s)" y / o puede haberlo simplificado para esta publicación.
Tom

Respuestas:

1735

Absolutamente, básicamente quieres:

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

O como una expresión sin consulta:

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

Básicamente, el contenido del grupo (cuando se ve como un IEnumerable<T>) es una secuencia de los valores que estaban en la proyección ( p.caren este caso) presente para la clave dada.

Para más información sobre cómo GroupByfunciona, vea mi publicación de Edulinq sobre el tema .

(He cambiado el nombre PersonIDa PersonIdlo anterior, para seguir las convenciones de nomenclatura de .NET ).

Alternativamente, puede usar un Lookup:

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

Luego puede obtener los autos para cada persona con mucha facilidad:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];
Jon Skeet
fuente
11
@jon Skeet, ¿y si quiero agregar otra propiedad como nombre
user123456
22
@Mohammad: Luego incluyes eso en el tipo anónimo.
Jon Skeet
77
@ user123456 aquí hay una buena explicación de group by, también incluye un ejemplo de agrupación por clave compuesta: Cómo: Agrupar resultados de consultas (Guía de programación de C #)
Mathieu Diepman
14
@Mohammad puedes hacer algo así .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})y obtendrás a todas las personas que ocurren con una identificación, nombre y una serie de ocurrencias para cada persona.
Chris
11
@kame: Estaba siguiendo deliberadamente convenciones de nomenclatura de .NET, arreglando los nombres del OP, básicamente. Lo aclarará en la respuesta.
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()/**/}
Tallat
fuente
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) };
Yogendra Paudyal
fuente
32

También puedes probar esto:

var results= persons.GroupBy(n => new { n.PersonId, n.car})
                .Select(g => new {
                               g.Key.PersonId,
                               g.Key.car)}).ToList();
shuvo sarker
fuente
Es incorrecto, devuelve la misma lista que no agrupa
Vikas Gupta
Es trabajo, creo que falta algo, por eso no funciona en su código.
shuvo sarker
29

tratar

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

o

para verificar si alguna persona está repitiendo en su lista intente

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)
Código Primero
fuente
13

He creado una muestra de código de trabajo con Query Syntax y Method Syntax. Espero que ayude a los demás :)

También puede ejecutar el código en .Net Fiddle aquí:

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

Aquí está el resultado:

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

Recev Yildiz
fuente
Por favor, explique qué hace su código por el bien. Esta es solo una respuesta de código que es casi una respuesta incorrecta.
V.7
2

Prueba esto :

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

Pero en cuanto al rendimiento, la siguiente práctica es mejor y más optimizada en el uso de la memoria (cuando nuestra matriz contiene muchos más elementos, como millones):

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];
akazemis
fuente
44
g.Key.PersonId? g.SelectMany?? Claramente no intentaste esto.
Gert Arnold
Estás escribiendo. Edité algunos códigos de códigos y no lo probé. Mi punto principal fue la segunda parte. Pero de todos modos, gracias por su consideración. Era demasiado tarde para editar ese código cuando me di cuenta de que estaba mal. ¡g.Key reemplaza g.Key.PersonId y Select en lugar de SelectMany! tan desordenado lo siento :)))
akazemis
2
@akazemis: En realidad estaba tratando de crear (para usar términos equivalentes al dominio de OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Lo más cerca que pude estar usando LINQ fue IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Terminé usando su formétodo de bucle que, por cierto, fue 2 veces más rápido. Además, usaría: a) foreachvs. fory b) TryGetValuevs. ContainsKey(ambos para el principio DRY - en código y tiempo de ejecución).
Tom
1

Una forma alternativa de hacer esto podría ser seleccionar distintos PersonIdy unirse a grupos 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 mismo con la sintaxis API fluida:

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 una lista de entradas en la primera lista (lista de PersonIden nuestro caso), cada una con un grupo de entradas unidas en la segunda lista (lista de persons).

Dmitry Stepanov
fuente
1

El siguiente ejemplo utiliza el método GroupBy para devolver objetos agrupados por 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());

O puede usar ToLookup, Básicamente ToLookupusa EqualityComparer<TKey>.Default para comparar claves y hacer lo que debe hacer manualmente cuando usa group by y to dictionary. creo que es exhumado inmemory

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

Primero, configure su campo clave. Luego incluya sus otros campos:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()
usuario3474287
fuente
La respuesta / código no comentado no es la forma en que funciona StackOverflow ...
V.7