[로봇활용_12주차] C# LINQ group by (데이터 분류)

최윤호·2025년 10월 30일
post-thumbnail

흩어진 데이터를 깔끔하게 분류

C#에서 데이터를 특정 기준에 따라 '분류'하고 싶을 때가 많습니다.

  • 온라인 쇼핑몰의 상품을 카테고리별로 묶어서 보여주기
  • 게임 플레이어 목록을 직업별로 나누어 통계를 내기
  • 학교의 학생 명단을 학년별로 정리하기

이런 작업을 하려면 어떻게 해야 할까요? Dictionary를 만들고,
foreach를 돌면서 키가 있는지 확인하고, 없으면 새로 만들고,
있으면 기존 리스트에 추가하고... 생각만 해도 코드가 복잡해질 것 같지 않나요?
이런 데이터 분류 작업을 해결해 주는 LINQ의 group by에 대해 알아보겠습니다!

1)group by 없이 데이터 분류

group by의 소중함을 느끼기 위해, 먼저 group by없이
"직업별로 플레이어 목록을 분류하는" 코드를 작성해 보겠습니다.

[코드]

using System;
using System.Collections.Generic;
using System.Linq; // LINQ를 사용하기 위해 필요

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public string Class { get; set; }
}

class Program
{
    static void Main()
    {
        // 데이터 준비
        var players = new List<Player>
        {
            new Player { Name = "용기사", Level = 45, Class = "Warrior" },
            new Player { Name = "마법사", Level = 50, Class = "Mage" },
            new Player { Name = "성기사", Level = 32, Class = "Warrior" },
            new Player { Name = "추적자", Level = 28, Class = "Thief" },
            new Player { Name = "주술사", Level = 38, Class = "Mage" },
            new Player { Name = "전사", Level = 15, Class = "Warrior" }
        };

        // players 리스트를 직업(Class)별로 묶어보겠습니다.
        // Dictionary<직업이름, 플레이어리스트>
        var playersByClass = new Dictionary<string, List<Player>>();

        foreach (var player in players)
        {
            // 만약 사전에 해당 직업 키가 없다면,
            if (!playersByClass.ContainsKey(player.Class))
            {
                // 새로운 리스트를 생성해서 사전에 추가
                playersByClass[player.Class] = new List<Player>();
            }
            // 해당 직업 키의 리스트에 현재 플레이어를 추가
            playersByClass[player.Class].Add(player);
        }

        // 결과 출력
        foreach (var group in playersByClass)
        {
            Console.WriteLine($"--- {group.Key} ---"); // 직업 이름 (Key)
            foreach (var player in group.Value) // 플레이어 리스트 (Value)
            {
                Console.WriteLine($"  이름: {player.Name}, 레벨: {player.Level}");
            }
        }
    }
}

[실행 결과]

--- Warrior ---
  이름: 용기사, 레벨: 45
  이름: 성기사, 레벨: 32
  이름: 전사, 레벨: 15
--- Mage ---
  이름: 마법사, 레벨: 50
  이름: 주술사, 레벨: 38
--- Thief ---
  이름: 추적자, 레벨: 28

결과는 잘 나오지만, '분류'라는 목적에 비해 코드가 너무 길고 복잡합니다.
우리는 '어떻게(How)' 묶을지에 대한 구현 코드를 전부 작성해야 했죠.

2)group by로 데이터 묶기

이제 LINQ의 group by를 사용해 위 코드를 다시 작성해 보겠습니다.

[코드]

using System;
using System.Collections.Generic;
using System.Linq; // LINQ를 사용하기 위해 필요

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public string Class { get; set; }
}

class Program
{
    static void Main()
    {
        // 데이터 준비
        var players = new List<Player>
        {
            new Player { Name = "용기사", Level = 45, Class = "Warrior" },
            new Player { Name = "마법사", Level = 50, Class = "Mage" },
            new Player { Name = "성기사", Level = 32, Class = "Warrior" },
            new Player { Name = "추적자", Level = 28, Class = "Thief" },
            new Player { Name = "주술사", Level = 38, Class = "Mage" },
            new Player { Name = "전사", Level = 15, Class = "Warrior" }
        };

        var groupedPlayers = from player in players
                             group player by player.Class;

        // 결과 출력
        foreach (var group in groupedPlayers)
        {
            Console.WriteLine($"--- {group.Key} ---"); // 그룹의 기준이 된 값 (직업 이름)
            foreach (var player in group) // 그룹에 속한 플레이어들
            {
                Console.WriteLine($"  이름: {player.Name}, 레벨: {player.Level}");
            }
        }
    }
}

[실행 결과]

--- Warrior ---
  이름: 용기사, 레벨: 45
  이름: 성기사, 레벨: 32
  이름: 전사, 레벨: 15
--- Mage ---
  이름: 마법사, 레벨: 50
  이름: 주술사, 레벨: 38
--- Thief ---
  이름: 추적자, 레벨: 28

단 한 줄의 쿼리로 복잡한 로직이 모두 대체되었습니다!

3)group by 문법

group [그룹화할 요소] by [그룹화 기준(Key)]

  • group player by player.Class:
    "players 리스트의 각 playerplayer.Class값을 기준으로 그룹화"하라는 의미입니다.

group by의 결과는 조금 특별합니다. 그냥 리스트가 아니라, 그룹들의 컬렉션이 반환돼요.
각 그룹은 Key와 해당 Key에 속하는 요소들의 묶음으로 이루어져 있습니다.

  • group.Key: 그룹을 묶은 기준이 되는 값입니다.
    위 예제에서는 'Warrior', 'Mage', 'Thief'와 같은 직업 이름이 되겠죠.
  • group: Key에 해당하는 요소들의 컬렉션입니다.
    foreach를 통해 그룹에 속한 player들을 하나씩 꺼내 쓸 수 있습니다.

마치 서점에서 책을 장르(Key)별로 책꽂이(group)를 만들고,
각 책꽂이에는 해당 장르의 책들(player)이 꽂혀있는 모습과 흡사합니다!

4)into로 그룹에 새 이름 붙이기

group by로 그룹을 만든 뒤, 추가적인 작업을 하고 싶을 때가 있습니다.
예를 들어 "그룹에 속한 멤버가 '2명 이상인 직업'만 골라내기" 같은 경우죠.
이럴 때 into 키워드가 유용하게 쓰입니다.

into는 생성된 그룹에 새로운 이름을 붙여, 그 그룹을 대상으로
where, orderby, select같은 추가적인 쿼리를 날릴 수 있게 해줍니다.

[코드]

using System;
using System.Collections.Generic;
using System.Linq; // LINQ를 사용하기 위해 필요

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public string Class { get; set; }
}

class Program
{
    static void Main()
    {
        // 데이터 준비
        var players = new List<Player>
        {
            new Player { Name = "용기사", Level = 45, Class = "Warrior" },
            new Player { Name = "마법사", Level = 50, Class = "Mage" },
            new Player { Name = "성기사", Level = 32, Class = "Warrior" },
            new Player { Name = "추적자", Level = 28, Class = "Thief" },
            new Player { Name = "주술사", Level = 38, Class = "Mage" },
            new Player { Name = "전사", Level = 15, Class = "Warrior" }
        };

        // 직업별로 그룹화한 뒤, 그룹 멤버가 2명 이상인 그룹만 선택해서
        // "직업 이름"과 "평균 레벨"을 출력하기
        var classStats = from player in players
                         group player by player.Class into classGroup
                         where classGroup.Count() >= 2
                         select new
                         {
                             Role = classGroup.Key,
                             Count = classGroup.Count(),
                             AvgLv = classGroup.Average(p => p.Level)
                         };

        foreach (var stat in classStats)
        {
            Console.WriteLine($"{stat.Role}: {stat.Count}명, 평균 Lv: {stat.AvgLv:F2}");
        }
    }
}

[실행 결과]

Warrior: 3명, 평균 Lv: 30.67
Mage: 2명, 평균 Lv: 44.00
  • group player by player.Class into classGroup: player.Class로 그룹을 만들고,
    그 그룹 덩어리에 classGroup이라는 이름을 붙였습니다.
  • where classGroup.Count() >= 2: 이제 그룹 자체(classGroup)를 하나의 단위로
    보고 조건을 걸 수 있습니다. 그룹 내 요소의 개수를 세는 Count()를 사용했네요.
  • select new { ... }: 그룹의 정보(classGroup.Key, classGroup.Count(), classGroup.Average(...))를 사용해 우리가 원하는 최종 결과물을 만들어냅니다.

5)정리

복잡한 반복문과 조건문 대신 group by를 사용하면,
코드의 의도가 명확해지고 훨씬 간결하게 데이터 분류 작업을 처리할 수 있습니다.
데이터 분석이나 통계 작업이 필요할 때 꼭 한번 활용해 보세요!

키워드역할
group by지정된 Key를 기준으로 데이터를 여러 그룹으로 나눕니다.
Key각 그룹을 대표하는 기준 값입니다.
into생성된 그룹 자체를 대상으로 추가적인 쿼리(필터링, 정렬 등)를 가능하게 합니다.
profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글