C++23에서는 범위(Range) 라이브러리에 더욱 강력한 데이터 처리 도구를 제공하기 위해 다양한 뷰(View) 어댑터를 추가했습니다. 그중에서도 std::views::chunk와 std::views::chunk_by는 입력 범위를 일정 크기나 조건에 따라 덩어리(Chunk)로 나누어 다룰 수 있게 해주는 기능입니다. 이를 통해 대량의 데이터를 부분 단위로 나누어 처리하거나, 특정 조건에 따라 그룹핑(grouping) 로직을 간단하게 구현할 수 있습니다.
이번 글에서는 std::views::chunk와 std::views::chunk_by의 개념과 사용법, 그리고 이전 방식과 비교하여 어떤 점이 개선되었는지 알아보겠습니다.
std::views::chunk와 std::views::chunk_by란 무엇인가요?
- std::views::chunk(n): 입력 범위를 길이 n의 고정 크기 청크로 나누어, 각 청크를 하나의 부분 범위로 제공하는 뷰 어댑터입니다. 예를 들어 [1,2,3,4,5,6,7]에 대해 chunk(3)를 적용하면 [[1,2,3], [4,5,6], [7]]와 같이 분할된 범위를 얻을 수 있습니다(마지막 청크는 부족한 길이로 나올 수 있음).
- std::views::chunk_by(pred): 입력 범위를 주어진 조건자(predicate)에 따라 구분하는 뷰 어댑터입니다. pred는 인접한 두 원소를 비교하여 새로운 청크를 시작할지 여부를 결정합니다. 예를 들어 [1,1,2,2,2,3,3,1]에 대해 값이 바뀔 때마다 새 청크를 시작하는 chunk_by를 적용하면 [[1,1], [2,2,2], [3,3], [1]] 형태를 얻을 수 있습니다.
이들 뷰를 사용하면 데이터 스트림이나 대량의 시퀀스를 일정 단위로 쉽게 처리하거나, 특정 패턴에 따라 그룹화하는 로직을 간결하게 표현할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++20까지는 범위를 일정 크기 단위로 나누거나 특정 조건으로 그룹핑하려면 수동으로 반복문을 작성하거나 iterator를 조작해서 새로운 컨테이너에 부분 범위를 복사하는 식의 번거로운 과정을 거쳐야 했습니다.
예제: 기존 방식(C++20까지)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5,6,7};
std::size_t chunk_size = 3;
// 수동으로 chunk 나누기
for (std::size_t i = 0; i < vec.size(); i += chunk_size) {
std::size_t end = std::min(i + chunk_size, vec.size());
// [i, end) 구간이 하나의 청크
for (std::size_t j = i; j < end; ++j) {
std::cout << vec[j] << ' ';
}
std::cout << '\n';
}
return 0;
}
- 문제점: 인덱스 계산, end 범위 처리 등 보일러플레이트 코드가 많고 가독성이 떨어집니다.
- 조건 기반 그룹핑은 더욱 복잡하게 구현해야 하며, iterator 조작이 필수입니다.
C++23의 std::views::chunk 사용 예제
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5,6,7};
// chunk(3)로 길이 3짜리 청크 단위로 분할
auto chunked = vec | std::views::chunk(3);
for (auto subrange : chunked) {
for (int x : subrange) {
std::cout << x << ' ';
}
std::cout << '\n';
}
// 출력:
// 1 2 3
// 4 5 6
// 7
return 0;
}
- 코드가 매우 간결해지고, index 계산이나 boundary 처리가 필요 없음.
C++23의 std::views::chunk_by 사용 예제
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,1,2,2,2,3,3,1};
// 인접한 값이 바뀔 때마다 새 청크 시작
auto chunked_by = vec | std::views::chunk_by([](int a, int b) {
return a == b;
});
for (auto subrange : chunked_by) {
for (int x : subrange) {
std::cout << x << ' ';
}
std::cout << "| ";
}
// 출력: 1 1 | 2 2 2 | 3 3 | 1 |
return 0;
}
- chunk_by를 통해 값 변화에 따른 그룹화 로직을 한 줄의 람다로 표현할 수 있어 가독성과 유지보수성 향상.
어떻게 좋아졌나요?
- 코드 단순화: 별도의 인덱스 계산이나 iterator 조작 없이 범위를 단위별로 분할 가능.
- 가독성 향상: chunk, chunk_by 사용으로 로직 의도가 명확히 드러나, 데이터 파이프라인을 읽고 쓰기가 편리.
- 유연성 증대: chunk와 chunk_by는 다른 범위 어댑터(filter, transform, take, drop, join 등)와 쉽게 결합할 수 있어, 복잡한 데이터 처리 파이프라인을 간결하게 표현.
- 함수형 프로그래밍 스타일: 파이프라인 형태의 코드를 작성하며, 함수형 프로그래밍에서 흔히 볼 수 있는 그룹화 로직을 직관적으로 구현 가능.
주의 사항
- 마지막 청크 처리: chunk(n) 사용 시 마지막 청크는 n보다 작을 수 있습니다. 이 부분을 염두에 두어야 합니다.
- predicate 정의: chunk_by의 predicate는 인접 원소끼리 비교하므로, 올바른 논리로 그룹화하는지 주의 깊게 설계해야 합니다.
- 컴파일러 및 라이브러리 지원: C++23 기능이므로, 해당 기능을 지원하는 컴파일러와 표준 라이브러리가 필요합니다.
요약
C++23의 std::views::chunk와 std::views::chunk_by는 범위를 일정 크기나 조건에 따라 그룹핑할 수 있는 강력한 뷰 어댑터를 제공합니다. 이를 통해 대용량 데이터나 복잡한 시퀀스를 단순하고 직관적으로 처리할 수 있으며, 다른 범위 어댑터와 조합하여 더욱 풍부하고 유연한 데이터 파이프라인을 구축할 수 있습니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] std::views::slide (1) | 2024.12.09 |
---|---|
[C++23 새기능 소개] [[nodiscard("이유")]] 속성 강화 (0) | 2024.12.09 |
[C++23 새기능 소개] std::views::join_with (0) | 2024.12.09 |
[C++23 새기능 소개] std::views::zip & std::views::zip_transform (0) | 2024.12.09 |
[C++23 새기능 소개] std::ranges::lazy_split_view (0) | 2024.12.09 |