Agrupar por em LINQ

1063

Vamos supor que se tivermos uma classe como:

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

Agora eu tenho uma lista desta classe: List<Person> persons;

Agora, esta lista pode ter várias instâncias com as mesmas PersonID, por exemplo:

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 uma maneira de agrupar PersonIDe obter a lista de todos os carros que ele tem?

Por exemplo, o resultado esperado seria

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

Então, após o agrupamento, eu obteria:

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

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

Pelo que fiz até agora:

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

Alguém pode por favor me dizer a direção correta?

test123
fonte
1
Há um outro exemplo, incluindo Counte Sumaqui stackoverflow.com/questions/3414080/…
Desenvolvedor
@ Martin Källman: Eu concordo com Chris Walsh. Provavelmente, um aplicativo que possui a classe "Pessoa (s)" do OP (tabela) já teria uma classe "'normal'" "Pessoa (s)" (tabela) que possui o usu. Propriedades / colunas (ou seja, nome, sexo, DOB). A classe "Pessoa (s)" do OP (tabela) seria uma classe criança (tabela) da classe "'normal'" "Pessoa (s)" (tabela) (ou seja, uma classe "Item (s) de ordem)" (tabela) ) vs. uma classe "Ordem (ões)" (tabela)). O OP provavelmente não estava usando o nome real que ele usaria se estivesse no mesmo escopo que a classe "Tabela" e "ou" pessoa "normal" e "" ou "simplificado" para este post.
Tom

Respostas:

1735

Absolutamente - você basicamente quer:

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

Ou como uma expressão sem consulta:

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

Basicamente, o conteúdo do grupo (quando visto como um IEnumerable<T>) é uma sequência de quaisquer valores presentes na projeção ( p.carneste caso) presentes para a chave especificada.

Para saber mais sobre como GroupByfunciona, consulte minha publicação do Edulinq sobre o assunto .

(Eu tenho renomeado PersonIDpara PersonIdno exemplo acima, a seguir NET convenções de nomenclatura .)

Como alternativa, você pode usar um Lookup:

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

Você pode obter os carros para cada pessoa com muita facilidade:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];
Jon Skeet
fonte
11
@ Jon Skeet o que se eu quiser adicionar outra propriedade como nome
user123456
22
@ Mohammad: Então você inclui isso no tipo anônimo.
precisa saber é o seguinte
7
@ user123456 aqui está uma boa explicação do grupo por, ele também inclui um exemplo de agrupamento por uma chave composta: Como: Resultados Grupo consulta (C # guia de programação)
Mathieu Diepman
14
@ Mohammad, você pode fazer algo assim .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})e obterá todas as pessoas que ocorrem com um ID, nome e várias ocorrências para cada pessoa.
Chris
11
@ kame: Eu estava seguindo deliberadamente as convenções de nomenclatura do .NET, corrigindo os nomes dos OP, basicamente. Isso deixará claro na resposta.
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
fonte
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
fonte
32

Você também pode tentar isso:

var results= persons.GroupBy(n => new { n.PersonId, n.car})
                .Select(g => new {
                               g.Key.PersonId,
                               g.Key.car)}).ToList();
shuvo sarker
fonte
É errado retorna a mesma lista não
agrupada
É trabalho, acho que algo está faltando, por isso não funciona no seu código.
shuvo sarker
29

tentar

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

ou

para verificar se alguém está repetindo na sua lista, tente

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)
Code First
fonte
13

Eu criei um exemplo de código de trabalho com sintaxe de consulta e sintaxe de método. Espero que ajude os outros :)

Você também pode executar o código no .Net Fiddle aqui:

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

Aqui está o resultado:

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

Recev Yildiz
fonte
Por favor, explique o que seu código faz por causa. Esta é apenas uma resposta de código que é quase uma resposta errada.
V.7
2

Tente o seguinte:

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

Mas em termos de desempenho, a prática a seguir é melhor e mais otimizada no uso de memória (quando nossa matriz contém muito mais itens como milhões):

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
fonte
4
g.Key.PersonId? g.SelectMany?? Você claramente não tentou isso.
Gert Arnold
você está escrevendo, editei alguns códigos e não testei. Meu ponto principal foi a segunda parte. Mas de qualquer forma obrigado pela sua consideração. Era tarde demais para editar esse código quando percebi que estava errado. então g.Key substitui g.Key.PersonId e Select em vez de SelectMany! então bagunça desculpa :)))
akazemis
2
@akazemis: Na verdade, eu estava tentando criar (para usar termos equivalentes ao domínio do OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. O mais próximo que pude usar o LINQ foi IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Acabei usando o formétodo loop, que, btw, era 2x mais rápido. Além disso, eu usaria: a) foreachvs. foreb) TryGetValuevs. ContainsKey(ambos pelo princípio DRY - em código e tempo de execução).
Tom
1

Uma maneira alternativa de fazer isso pode ser selecionar distintos PersonIde ingressar no grupo 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 o mesmo com a sintaxe fluente da API:

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 produz uma lista de entradas na primeira lista (lista de PersonIdno nosso caso), cada uma com um grupo de entradas unidas na segunda lista (lista de persons).

Dmitry Stepanov
fonte
1

O exemplo a seguir usa o método GroupBy para retornar objetos agrupados por 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 você pode usar ToLookup, Basicamente ToLookupusa EqualityComparer<TKey>.Default para comparar chaves e fazer o que você deve fazer manualmente ao usar agrupar por e para dicionário. eu acho que é excutado em memória

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

Primeiro, defina seu campo-chave. Em seguida, inclua seus outros campos:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()
user3474287
fonte
Resposta / código não comentado não é a maneira como o StackOverflow funciona ... #
7.7