C++23에서는 범위(Range) 라이브러리에 풍부한 알고리즘들이 추가되어, 데이터 처리 시 다양한 시나리오를 표준화된 방식으로 해결할 수 있게 되었습니다. 그중 하나인 std::ranges::sample 알고리즘은 범위에서 무작위로 일부 원소를 추출하여 샘플링할 수 있도록 하는 기능을 제공합니다. 이를 통해 무작위 데이터 선정, 표본 추출 등의 작업을 표준 라이브러리 알고리즘만으로 간편히 구현할 수 있습니다.
이번 글에서는 std::ranges::sample의 개념과 사용법, 그리고 이전 방식과 비교하여 어떠한 개선점을 제공하는지 알아보겠습니다.
std::ranges::sample란 무엇인가요?
std::ranges::sample(rng, out, n, gen)는 범위 rng에서 원소를 n개 무작위로 선택하여, 그 결과를 out 출력 반복자에 저장하는 알고리즘입니다. gen은 난수 발생을 위한 엔진 또는 어댑터를 전달합니다. 이는 기존의 <algorithm> 헤더에 있는 std::sample 함수에 해당하는 범위 기반(std::ranges) 버전으로, C++23에서 추가되었습니다.
주요 특징:
- 범위 친화적: std::ranges::sample는 std::ranges:: 네임스페이스 아래 있으므로, 다른 범위 알고리즘이나 뷰와 자연스럽게 어울립니다.
- 난수 기반 샘플링: 지정된 난수 발생기를 이용하여 원소를 무작위로 선택, 특정 확률적 성질을 가진 표본 추출 가능.
- 단순 구현: std::sample와 유사한 문법이지만, 범위 기반 인터페이스로 가독성 향상.
이전 버전에서는 어떻게 했나요?
C++20까지는 <algorithm> 헤더에 std::sample이 존재했지만, 이는 범위 기반 인터페이스를 제공하지 않고, iterator 쌍을 인자로 받아야 했습니다. 범위 라이브러리를 사용할 때는 별도로 std::begin, std::end를 호출하거나, std::views와 결합하려면 번거로운 변환이 필요했습니다.
예제: 기존 방식(C++20)
#include <algorithm>
#include <random>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5};
std::vector<int> result;
result.resize(2);
std::mt19937 gen(std::random_device{}());
std::sample(vec.begin(), vec.end(), result.begin(), 2, gen);
for (int x : result) {
std::cout << x << ' ';
}
return 0;
}
- 문제점: iterator 기반 호출, 범위(View)와 직접적으로 결합하기 어려움.
C++23의 std::ranges::sample 사용 예제
#include <ranges>
#include <random>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1,2,3,4,5};
std::vector<int> result;
result.resize(2);
std::mt19937 gen(std::random_device{}());
// 범위 기반 호출
std::ranges::sample(vec, result.begin(), 2, gen);
for (int x : result) {
std::cout << x << ' ';
}
return 0;
}
- 범위 기반 호출로 가독성이 높아지고, 다른 범위 알고리즘과 결합하기 쉬움.
다른 뷰와의 조합 예시
#include <ranges>
#include <random>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {10,20,30,40,50,60};
std::vector<int> sample_res;
sample_res.resize(3);
std::mt19937 gen(std::random_device{}());
// 예: 짝수 원소만 필터링한 뒤 샘플링
auto evens = data | std::views::filter([](int x){ return x % 2 == 0; });
// evens 범위에서 무작위로 3개 샘플링 (단, evens의 크기 >= 3 가정)
std::ranges::sample(evens, sample_res.begin(), 3, gen);
for (int x : sample_res) {
std::cout << x << ' ';
}
return 0;
}
- filter 뷰와 결합해 조건을 만족하는 원소 집합에서만 샘플링 가능.
어떻게 좋아졌나요?
- 범위와 직접 결합: 다른 범위 기반 함수나 뷰와 쉽게 조합 가능, iterator 변환 필요 없음.
- 가독성 향상: 범위 기반 호출로 코드 의도 명확, 로직 단순화.
- 표준화된 인터페이스: 기존 std::sample와 동일한 기능을 범위 기반으로 제공하므로 이식성과 일관성 상승.
주의 사항
- 샘플 크기 주의: 요청한 샘플 크기 n이 원본 범위 크기보다 크면, 원본 범위의 전체 원소만 포함.
- 난수 발생기 품질: 결과는 난수 발생기의 특성에 따라 다르므로, 예측 가능한 난수 엔진 사용 시 예측 가능한 샘플링 결과.
- C++23 지원 여부: C++23 기능이므로, 지원하는 컴파일러와 표준 라이브러리 필요.
요약
C++23의 std::ranges::sample는 범위에서 무작위 샘플을 추출하는 기능을 범위 기반 인터페이스로 제공하여, 다른 뷰나 알고리즘과 쉽게 결합할 수 있게 합니다. 이를 통해 무작위 추출 로직을 기존 std::sample 대비 더욱 직관적으로 구현할 수 있으며, 코드 가독성과 유지보수성, 생산성을 모두 개선할 수 있습니다.
참고 자료:
'개발 이야기 > C++' 카테고리의 다른 글
[모던 C++ #2] C-Style 배열을 벗어나: std::array, std::vector 그리고 std::span으로! (0) | 2024.12.14 |
---|---|
[모던 C++ #1] 이제는 버려야 할 레거시: auto_ptr를 내려놓고 unique_ptr로! (0) | 2024.12.14 |
[C++23 새기능 소개] std::in_place_stop_source와 std::in_place_stop_token (0) | 2024.12.12 |
[C++23 새기능 소개] std::views::repeat와 std::views::repeat_n (1) | 2024.12.12 |
[C++23 새기능 소개] std::views::common (0) | 2024.12.12 |