[C++23 새기능 소개] std::ranges::sample

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 대비 더욱 직관적으로 구현할 수 있으며, 코드 가독성과 유지보수성, 생산성을 모두 개선할 수 있습니다.

 

 

참고 자료:

반응형