LINQ에서 그룹화


1061

다음과 같은 클래스가 있다고 가정 해 봅시다.

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

이제이 클래스의 목록이 있습니다. List<Person> persons;

이제이 목록은 같은 PersonID의 인스턴스를 여러 개 가질 수 있습니다 .

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

내가 그룹화하여 PersonID그가 가지고있는 모든 자동차의 목록을 얻을 수있는 방법 이 있습니까?

예를 들어 예상 결과는

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

따라서 그룹화 한 후 다음을 얻습니다.

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

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

내가 지금까지 한 일에서 :

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

누군가 올바른 방향으로 나를 가리켜 주시겠습니까?


1
를 포함하는 또 다른 예입니다 Count그리고 Sum여기 stackoverflow.com/questions/3414080/...
개발자

@Martin Källman : Chris Walsh에 동의합니다. OP의 "Person (s)"클래스 (테이블)가있는 앱은 아마도 usu가있는 ""normal "" "Person (s)"클래스 (테이블)를 가지고있을 것입니다. 속성 / 열 (예 : 이름, 성별, DOB) OP의 "Person (s)"클래스 (테이블)는 ""normal "" "Person (s)"클래스 (Table) (즉, "OrderItem (s)"클래스 (Table)의 하위 클래스 (테이블) 일 것입니다. ) vs. "주문"클래스 (표)). OP가 자신의 " '정상'" "개인"클래스 (테이블)와 동일한 범위에 있거나 사용했을 때 실제 이름을 사용하지 않았거나이 게시물에 대해 단순화했을 수 있습니다.
Tom

답변:


1732

절대적으로-당신은 기본적으로 원합니다 :

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

또는 비 쿼리 식으로 :

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

기본적으로 그룹의 내용 (로 볼 때 IEnumerable<T>)은 p.car주어진 키 에 대한 프로젝션 ( 이 경우) 에있는 값의 순서입니다 .

GroupBy작동 방식 에 대한 자세한 내용 은 주제에 대한 Edulinq 게시물을 참조하십시오 .

( 위에서 .NET 이름 지정 규칙 을 따르도록 이름 PersonID을 변경 했습니다 .)PersonId

또는 다음을 사용할 수 있습니다 Lookup.

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

그런 다음 각 사람의 자동차를 매우 쉽게 얻을 수 있습니다.

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

11
@jon Skeet 만약 이름과 같은 다른 속성을 추가하고 싶다면
user123456

22
@Mohammad : 그런 다음 익명 형식에 포함시킵니다.
Jon Skeet

7
:, 또한 복합 키에 의해 그룹화의 예를 포함하여 @ user123456 여기에 그룹의 좋은 설명입니다 방법에를 : 그룹 쿼리 결과 (C #을 가이드 프로그래밍)
마티유 Diepman

14
@Mohammad 당신은 같은 일을 할 수 .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})있으며 당신은 ID, 이름 및 각 사람에 대해 여러 번 발생하는 모든 사람들을 얻을 것입니다.
Chris

11
@ kame : 의도적으로 .NET 명명 규칙을 따르고 기본적으로 OP의 이름을 수정했습니다. 답을 분명히 할 것입니다.
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

당신은 또한 이것을 시도 할 수 있습니다 :

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

그것은 잘못된 목록입니다 같은 목록으로 그룹화하지 않음
Vikas Gupta

그것은 효과가 있습니다. 나는 뭔가 빠졌기 때문에 그것이 코드에서 작동하지 않는 이유입니다.
shuvo sarker

28

시험

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

또는

목록에 반복되는 사람이 있는지 확인

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

13

Query Syntax 및 Method Syntax를 사용하여 작업 코드 샘플을 작성했습니다. 나는 그것이 다른 사람들을 돕기를 바랍니다 :)

.Net Fiddle에서 코드를 실행할 수도 있습니다.

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

결과는 다음과 같습니다.

1
페라리
BMW
2
아우디
-----------
1
페라리
BMW
2
아우디


귀하의 코드가 어떤 역할을하는지 설명하십시오. 이것은 거의 잘못된 답변 인 코드 답변입니다.
V.7

2

이 시도 :

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

그러나 성능 측면에서는 다음과 같은 방법이 메모리 사용에서 더 우수하고 최적화되어 있습니다 (배열에 수백만 개가 훨씬 많은 항목이 포함 된 경우).

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?? 당신은 분명히 이것을 시도하지 않았습니다.
Gert Arnold

당신은 내가 코드 코드를 편집하고 테스트하지 않았다고 쓰고 있습니다. 내 요점은 두 번째 부분이었습니다. 어쨌든 고려해 주셔서 감사합니다. 코드가 잘못되었다는 것을 알게되면 코드를 편집하기에는 너무 늦었습니다. 따라서 g.Key는 g.Key.PersonId를 대체하고 SelectMany 대신 Select를 대체합니다. 너무 지저분한 죄송합니다 :)))
akazemis

2
@akazemis : 실제로 (OP의 도메인과 동등한 용어를 사용하기 위해) 만들려고했습니다 SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. LINQ를 사용할 수있는 가장 가까운 것은 IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>입니다. 나는 forbtw가 2 배 빠른 루프 방법을 사용하여 끝났습니다 . 또한, 내가 사용하는 것이다 : A) foreachfor와 b) TryGetValueContainsKey(모두 DRY에 대한 원칙 - 코드 및 런타임).
Tom

1

이를 수행하는 다른 방법은 다음 PersonIdpersons같이 구별 및 그룹 조인을 선택할 수 있습니다 .

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

또는 유창한 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 은 첫 번째 목록 (이 경우 목록)에 항목 목록을 생성하며 PersonId각 목록은 두 번째 목록 (목록 persons) 에 결합 된 항목 그룹을 갖습니다 .


1

다음 예제에서는 GroupBy 메서드를 사용하여로 그룹화 된 객체를 반환합니다 PersonID.

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

또는

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

또는

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

또는 ToLookup, 기본적으로 .Default를 ToLookup사용 EqualityComparer<TKey>하여 키를 비교하고 그룹 별 및 사전을 사용할 때 수동으로 수행해야 할 작업을 사용할 수 있습니다. 나는 그것이 추방 당했다고 생각합니다.

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

0

먼저 키 필드를 설정하십시오. 그런 다음 다른 필드를 포함하십시오.

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

주석이없는 답변 / 코드는 StackOverflow 작동 방식이 아닙니다 ...
V.7
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.