
C#에서 데이터를 특정 기준에 따라 '분류'하고 싶을 때가 많습니다.
이런 작업을 하려면 어떻게 해야 할까요? Dictionary를 만들고,
foreach를 돌면서 키가 있는지 확인하고, 없으면 새로 만들고,
있으면 기존 리스트에 추가하고... 생각만 해도 코드가 복잡해질 것 같지 않나요?
이런 데이터 분류 작업을 해결해 주는 LINQ의 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)' 묶을지에 대한 구현 코드를 전부 작성해야 했죠.
이제 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
단 한 줄의 쿼리로 복잡한 로직이 모두 대체되었습니다!
group [그룹화할 요소] by [그룹화 기준(Key)]
group player by player.Class:player를 player.Class값을 기준으로 그룹화"하라는 의미입니다.group by의 결과는 조금 특별합니다. 그냥 리스트가 아니라, 그룹들의 컬렉션이 반환돼요.
각 그룹은 Key와 해당 Key에 속하는 요소들의 묶음으로 이루어져 있습니다.
group.Key: 그룹을 묶은 기준이 되는 값입니다.Warrior', 'Mage', 'Thief'와 같은 직업 이름이 되겠죠.group: Key에 해당하는 요소들의 컬렉션입니다.foreach를 통해 그룹에 속한 player들을 하나씩 꺼내 쓸 수 있습니다.마치 서점에서 책을 장르(Key)별로 책꽂이(group)를 만들고,
각 책꽂이에는 해당 장르의 책들(player)이 꽂혀있는 모습과 흡사합니다!
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(...))를 사용해 우리가 원하는 최종 결과물을 만들어냅니다.복잡한 반복문과 조건문 대신 group by를 사용하면,
코드의 의도가 명확해지고 훨씬 간결하게 데이터 분류 작업을 처리할 수 있습니다.
데이터 분석이나 통계 작업이 필요할 때 꼭 한번 활용해 보세요!
| 키워드 | 역할 |
|---|---|
group by | 지정된 Key를 기준으로 데이터를 여러 그룹으로 나눕니다. |
Key | 각 그룹을 대표하는 기준 값입니다. |
into | 생성된 그룹 자체를 대상으로 추가적인 쿼리(필터링, 정렬 등)를 가능하게 합니다. |