C++23에서는 범위(Range) 라이브러리에 다양한 뷰(View) 어댑터가 추가되어, 데이터 처리 파이프라인을 더욱 간결하고 표현력 있게 만들 수 있습니다. 그중 하나인 std::views::slide는 입력 범위에 대해 슬라이딩 윈도우(sliding window)를 적용하는 뷰 어댑터입니다. 이를 통해 고정 크기의 창(window)을 범위를 따라 이동시키면서 각 위치에서의 부분 범위를 쉽게 얻을 수 있습니다.
이번 글에서는 std::views::slide의 개념과 사용법, 그리고 이전 방식과 비교하여 어떠한 개선점을 제공하는지 알아보겠습니다.
std::views::slide란 무엇인가요?
std::views::slide(n)는 입력 범위에 대해 길이 n의 윈도우를 순회하면서, 각 위치에서 길이 n짜리의 부분 범위를 뷰로 제공하는 어댑터입니다. 창(window)은 1칸씩 전진하며, 각 전진 시마다 새로운 부분 범위(서브레인지)를 반환합니다.
예를 들어, [1,2,3,4,5] 범위에 slide(3)를 적용하면 다음과 같은 부분 범위를 순차적으로 제공합니다.
- 첫 번째 위치: [1,2,3]
- 두 번째 위치: [2,3,4]
- 세 번째 위치: [3,4,5]
이렇게 연속적인 요소를 일정 크기 단위로 슬라이딩하면서 검사하는 패턴은 통계, 신호 처리, 머신러닝(윈도우 평균, 이동 평균), 문자열 처리(부분 문자열 검색), 게임 로직(인접 3개 요소 비교) 등 다양한 분야에서 유용하게 활용할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++20까지는 슬라이딩 윈도우 로직을 구현하기 위해 다음과 같은 방법이 필요했습니다.
예제: 기존 방식(C++20까지)
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5};
std::size_t window_size = 3;
// 인덱스를 활용한 수동 구현
for (std::size_t start = 0; start + window_size <= vec.size(); ++start) {
for (std::size_t i = start; i < start + window_size; ++i) {
std::cout << vec[i] << ' ';
}
std::cout << '\n';
}
return 0;
}
- 문제점: 인덱스 계산, boundary 체크, 각 윈도우를 서브컨테이너로 추출하는 과정 등이 번거롭고, 로직이 장황합니다.
- 파이프라인 형태로 다른 범위 어댑터와 결합하기도 어렵습니다.
C++23의 std::views::slide 사용 예제
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5};
// slide(3)을 적용하여 길이 3의 윈도우 이동
auto windows = vec | std::views::slide(3);
for (auto window : windows) {
for (int x : window) {
std::cout << x << ' ';
}
std::cout << '\n';
}
// 출력:
// 1 2 3
// 2 3 4
// 3 4 5
return 0;
}
- 코드가 훨씬 간결해지고 의도가 명확해졌습니다.
- windows는 [[1,2,3],[2,3,4],[3,4,5]]와 같은 부분 범위들의 뷰를 제공합니다.
어떻게 좋아졌나요?
- 코드 간결성: 인덱스 계산이나 boundary 처리 등 보일러플레이트 코드가 필요 없어집니다.
- 가독성 향상: slide를 보는 순간 슬라이딩 윈도우 로직을 직관적으로 파악할 수 있습니다.
- 함수형 프로그래밍 스타일: 다른 범위 어댑터(filter, transform, take, join 등)와 결합이 쉬워, 복잡한 데이터 파이프라인도 한 줄에 표현 가능.
- 유연성: 윈도우 크기만 바꾸면 손쉽게 다른 단위로 처리 가능.
상세한 예제와 비교
다른 뷰와의 조합
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1,2,3,4,5,6,7};
// slide(3) 후 각 윈도우의 합계를 구하는 예제
// zip_transform과 함께 사용 가능하지만 단순 transform 사용 예시
auto window_sums = data
| std::views::slide(3)
| std::views::transform([](auto window) {
int sum = 0;
for (int x : window) sum += x;
return sum;
});
for (int s : window_sums) {
std::cout << s << ' ';
}
// 출력: 6 (1+2+3), 9 (2+3+4), 12 (3+4+5), 15 (4+5+6), 18 (5+6+7)
return 0;
}
- slide를 통해 얻은 각 윈도우를 transform을 통해 합계로 변환하는 로직을 쉽게 구현.
주의 사항
- 윈도우 크기 관리: slide(n)에서 n이 범위보다 크면 마지막 부분 윈도우는 줄어든 형태로 나옵니다. 이 점을 알고 로직을 설계해야 합니다.
- 컴파일러 및 라이브러리 지원: C++23 기능이므로, 해당 기능을 지원하는 컴파일러와 표준 라이브러리가 필요합니다.
- 성능 고려: 슬라이딩 윈도우 처리 시, 큰 데이터에 대해서는 성능 최적화를 고려할 수 있습니다. 그러나 범위 뷰 자체는 lazy evaluation을 통해 불필요한 계산을 줄여줍니다.
요약
C++23의 std::views::slide는 범위에 슬라이딩 윈도우를 적용하는 기능을 제공함으로써 데이터 처리 파이프라인을 한층 강화합니다. 인덱스 기반 접근 없이 간결한 코드로 윈도우 단위의 처리 로직을 구현할 수 있으며, 다른 범위 어댑터와의 조합으로 복잡한 데이터 처리 파이프라인을 손쉽게 구성할 수 있습니다. 이를 통해 가독성, 유지보수성, 코드 품질과 생산성이 크게 향상됩니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] std::ranges::starts_with, std::ranges::ends_with, std::ranges::contains (0) | 2024.12.09 |
---|---|
[C++23 새기능 소개] std::to_underlying (0) | 2024.12.09 |
[C++23 새기능 소개] [[nodiscard("이유")]] 속성 강화 (0) | 2024.12.09 |
[C++23 새기능 소개] std::views::chunk & std::views::chunk_by (0) | 2024.12.09 |
[C++23 새기능 소개] std::views::join_with (0) | 2024.12.09 |