C++ STL 알고리즘 완벽 가이드 | sort·transform·accumulate [#54-1]

C++ STL 알고리즘 완벽 가이드 | sort·transform·accumulate [#54-1]

이 글의 핵심

STL 알고리즘 마스터: sort, binary_search, transform, accumulate, 커스텀 비교자. 문제 시나리오, 완전한 예제, 흔한 에러, 성능 팁, 프로덕션 패턴.

들어가며: STL 알고리즘으로 반복 코드를 제거하자

”매번 for문 작성하다 보니 버그가 자꾸 생겨요”

데이터 처리 코드를 작성할 때마다 직접 반복문을 작성하다 보면, 인덱스 오류, 경계 조건 실수, 성능 이슈가 반복됩니다. C++ STL 알고리즘은 <algorithm>, <numeric>에 정의된 검증된 구현으로, 정렬·검색·변환·집계를 한 줄로 표현하면서 직접 루프보다 실수도 적고 성능도 나쁘지 않습니다. 이 글에서는 sort·binary_search·transform·accumulate를 중심으로, 문제 시나리오부터 프로덕션 패턴까지 실전 활용법을 다룹니다.

요구 환경: C++17 이상

이 글을 읽으면:

  • 핵심 STL 알고리즘을 완전히 이해할 수 있습니다.
  • 문제 상황별 적절한 알고리즘을 선택할 수 있습니다.
  • 흔한 에러를 피하고 성능을 최적화할 수 있습니다.
  • 프로덕션 수준의 패턴을 적용할 수 있습니다.

실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 문제 시나리오
  2. 정렬 알고리즘 (sort)
  3. 검색 알고리즘 (binary_search)
  4. 변환 알고리즘 (transform)
  5. 집계 알고리즘 (accumulate)
  6. 완전한 STL 알고리즘 예제
  7. 자주 발생하는 에러와 해결법
  8. 성능 최적화 팁
  9. 프로덕션 패턴

1. 문제 시나리오

시나리오 1: 대량 로그 데이터 정렬

상황: 수백만 건의 로그를 타임스탬프 기준으로 정렬해야 합니다. 직접 버블 정렬이나 삽입 정렬을 구현하면 O(n²)로 시간 초과가 발생합니다.

해결: std::sort는 내부적으로 인트로소트(퀵소트 + 힙소트 + 삽입정렬)를 사용해 O(n log n)에 정렬합니다. 같은 값의 순서가 중요하면 std::stable_sort를 사용합니다.

시나리오 2: 정렬된 배열에서 빠른 검색

상황: 이미 정렬된 100만 개 ID 목록에서 특정 ID가 있는지 확인해야 합니다. std::find로 선형 검색하면 O(n)으로 느립니다.

해결: std::binary_search는 O(log n)에 “존재 여부”만 반환하고, std::lower_bound/upper_bound로 위치나 개수를 구할 수 있습니다.

시나리오 3: 데이터 변환 파이프라인

상황: JSON 파싱 결과를 DTO로 변환하거나, 센서 값을 정규화하는 등 “각 원소에 함수 적용”이 반복됩니다. for문으로 작성하면 가독성이 떨어지고 실수하기 쉽습니다.

해결: std::transform으로 변환 로직을 선언적으로 표현하고, 람다와 조합해 복잡한 변환도 한 줄로 처리합니다.

시나리오 4: 집계 연산 (합계, 곱셈, 문자열 연결)

상황: 벡터의 합, 곱, 또는 문자열 리스트를 하나로 연결하는 등 “접기(fold)” 연산이 필요합니다. 수동 루프는 초기값 처리나 타입 변환에서 실수하기 쉽습니다.

해결: std::accumulate로 초기값부터 순차적으로 이항 함수를 적용해 집계합니다. <numeric> 헤더가 필요합니다.

STL 알고리즘 분류 다이어그램

flowchart TB
    subgraph sort[정렬]
        S1[sort]
        S2[stable_sort]
        S3[partial_sort]
    end
    subgraph search[검색]
        F1[binary_search]
        F2[lower_bound]
        F3[upper_bound]
    end
    subgraph transform[변환]
        T1[transform]
        T2[transform 두 범위]
    end
    subgraph agg[집계]
        A1[accumulate]
        A2[reduce C++17]
    end
    input["[begin, end) 반복자"] --> sort
    input --> search
    input --> transform
    input --> agg

2. 정렬 알고리즘 (sort)

std::sort: 기본 정렬

std::sort반개구간 [begin, end) 에 있는 원소를 기본적으로 오름차순으로 정렬합니다. 내부적으로 인트로소트를 사용하며, 비교 연산만 정의되어 있으면 어떤 타입이든 정렬할 수 있습니다.

// g++ -std=c++17 -o sort_basic sort_basic.cpp && ./sort_basic
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    std::sort(vec.begin(), vec.end());
    for (int x : vec) std::cout << x << " ";  // 1 2 3 5 7 8 9
    std::cout << "\n";

    // 내림차순
    std::sort(vec.begin(), vec.end(), std::greater<int>());
    for (int x : vec) std::cout << x << " ";  // 9 8 7 5 3 2 1
    std::cout << "\n";
    return 0;
}

주의: std::sort같은 값끼리의 원래 순서를 보장하지 않습니다. 같은 값의 순서가 중요하면 std::stable_sort를 사용하세요.

커스텀 비교자

구조체나 클래스를 정렬할 때는 비교 함수로 기준을 넘깁니다. 람다 { return a.x < b.x; }는 “a가 b보다 앞에 오려면 a.x < b.x”를 의미합니다.

#include <algorithm>
#include <vector>
#include <string>

struct LogEntry {
    std::string timestamp;
    int level;
    std::string message;
};

int main() {
    std::vector<LogEntry> logs = {
        {"2026-04-23 10:00:00", 2, "Warning"},
        {"2026-04-23 09:00:00", 1, "Info"},
        {"2026-04-23 10:00:00", 1, "Error"}
    };

    // 타임스탬프 오름차순, 같으면 level 오름차순
    std::sort(logs.begin(), logs.end(),
         {
            if (a.timestamp != b.timestamp)
                return a.timestamp < b.timestamp;
            return a.level < b.level;
        });
}

stable_sort: 안정 정렬

같은 값끼리 원래 입력 순서를 유지해야 할 때 사용합니다. 메모리를 더 사용하지만, “점수로 정렬하되 같은 점수면 제출 순서 유지” 같은 요구사항에 맞습니다.

#include <algorithm>
#include <vector>

struct Student {
    std::string name;
    int score;
};

int main() {
    std::vector<Student> students = {
        {"Alice", 90}, {"Bob", 85}, {"Charlie", 90}, {"David", 85}
    };

    std::stable_sort(students.begin(), students.end(),
         { return a.score > b.score; });
    // Alice(90), Charlie(90), Bob(85), David(85) - 같은 점수 내 순서 유지
}

partial_sort: 상위 k개만 정렬

전체를 정렬할 필요 없이 “상위 N개만 올바른 순서”가 필요할 때 사용합니다. “상위 10명만 뽑기” 같은 경우 전체 정렬보다 효율적입니다.

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};

    // 상위 3개만 정렬 (가장 큰 3개)
    std::partial_sort(vec.begin(), vec.begin() + 3, vec.end(),
                      std::greater<int>());
    // vec: {9, 8, 7, ...} (나머지 순서 보장 안 됨)

    for (int i = 0; i < 3; ++i)
        std::cout << vec[i] << " ";  // 9 8 7
    std::cout << "\n";
    return 0;
}

전제 조건: 범위가 정렬되어 있어야 함

std::binary_search, std::lower_bound, std::upper_bound정렬된 범위에서만 올바르게 동작합니다. 정렬되지 않은 범위에 사용하면 undefined behavior입니다.

std::binary_search: 존재 여부만 확인

O(log n)에 “값이 존재하는지” true/false만 반환합니다. 위치(반복자)가 필요하면 lower_bound/upper_bound를 사용하세요.

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    if (std::binary_search(vec.begin(), vec.end(), 5)) {
        std::cout << "5 exists\n";
    }

    if (!std::binary_search(vec.begin(), vec.end(), 10)) {
        std::cout << "10 not found\n";
    }
    return 0;
}

std::lower_bound: “이 값 이상”인 첫 위치

정렬된 범위에서 value 이상인 첫 번째 원소의 반복자를 반환합니다. 같은 값이 여러 개일 때 그 구간의 시작을 알 수 있습니다.

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 2, 2, 3, 4, 5};

    auto lower = std::lower_bound(vec.begin(), vec.end(), 2);
    std::cout << "Index: " << std::distance(vec.begin(), lower) << "\n";  // 1

    // 2의 개수
    auto upper = std::upper_bound(vec.begin(), vec.end(), 2);
    std::cout << "Count of 2: " << std::distance(lower, upper) << "\n";  // 3
    return 0;
}

std::upper_bound: “이 값 초과”인 첫 위치

value보다 큰 첫 번째 원소의 반복자를 반환합니다. [lower_bound, upper_bound) 구간이 value와 같은 모든 원소입니다.

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 2, 2, 3, 4, 5};

    auto lower = std::lower_bound(vec.begin(), vec.end(), 2);
    auto upper = std::upper_bound(vec.begin(), vec.end(), 2);

    // [lower, upper) 구간이 값 2인 모든 원소
    for (auto it = lower; it != upper; ++it) {
        // *it == 2
    }
}

equal_range: lower_bound + upper_bound 한 번에

std::equal_rangelower_boundupper_bound를 한 번에 호출한 것과 동일한 std::pair를 반환합니다. 같은 값의 구간을 한 번에 얻을 때 유용합니다.

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 2, 2, 3, 4, 5};

    auto [lower, upper] = std::equal_range(vec.begin(), vec.end(), 2);
    size_t count = std::distance(lower, upper);  // 3
}

4. 변환 알고리즘 (transform)

std::transform: 단항 변환

범위의 각 원소에 함수(람다)를 적용한 결과를 출력 범위에 씁니다. 출력을 입력과 같은 범위로 주면 제자리(in-place) 변환이 됩니다.

#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int> result(vec.size());

    // 각 원소 2배
    std::transform(vec.begin(), vec.end(), result.begin(),
                   { return x * 2; });
    // result: {2, 4, 6, 8, 10}

    // 제자리 변환 (원본 수정)
    std::transform(vec.begin(), vec.end(), vec.begin(),
                   { return x * 2; });
    // vec: {2, 4, 6, 8, 10}

    // 실수 변환 (sqrt)
    std::vector<double> values = {1.0, 4.0, 9.0, 16.0};
    std::vector<double> roots(values.size());
    std::transform(values.begin(), values.end(), roots.begin(),
                   { return std::sqrt(x); });
    // roots: {1, 2, 3, 4}
    return 0;
}

std::transform: 두 범위 결합 (이항 변환)

두 시퀀스의 같은 위치 원소를 한 번에 받아 결과를 출력합니다. 벡터 덧셈, 점곱 등에 활용합니다.

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> a = {1, 2, 3, 4, 5};
    std::vector<int> b = {10, 20, 30, 40, 50};
    std::vector<int> sum(a.size());

    std::transform(a.begin(), a.end(), b.begin(), sum.begin(),
                   { return x + y; });
    // sum: {11, 22, 33, 44, 55}

    // 두 범위 길이가 다르면 짧은 쪽까지만 처리됨
    return 0;
}

DTO 변환 예제

JSON 파싱 결과를 DTO로 변환하는 실전 패턴입니다.

#include <algorithm>
#include <vector>
#include <string>

struct JsonRecord {
    std::string id;
    std::string name;
    int value;
};

struct Dto {
    int id;
    std::string name;
    double normalized_value;
};

std::vector<Dto> toDtos(const std::vector<JsonRecord>& records, double scale) {
    std::vector<Dto> result;
    result.reserve(records.size());
    std::transform(records.begin(), records.end(), std::back_inserter(result),
        [scale](const JsonRecord& r) {
            return Dto{
                std::stoi(r.id),
                r.name,
                static_cast<double>(r.value) / scale
            };
        });
    return result;
}

5. 집계 알고리즘 (accumulate)

std::accumulate: 기본 동작

std::accumulate왼쪽부터 차례로 접어 나가는(fold) 연산입니다. 세 번째 인자는 초기값이고, 기본 동작은 초기값 + vec[0] + vec[1] + ...입니다. <numeric> 헤더가 필요합니다.

flowchart LR
    I["초기값 0"] --> A1["+ vec[0]"]
    A1 --> A2["+ vec[1]"]
    A2 --> A3["+ vec[2]"]
    A3 --> R["최종 결과"]
#include <numeric>
#include <vector>
#include <string>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    int sum = std::accumulate(vec.begin(), vec.end(), 0);
    std::cout << "Sum: " << sum << "\n";  // 15

    int product = std::accumulate(vec.begin(), vec.end(), 1,
         { return a * b; });
    std::cout << "Product: " << product << "\n";  // 120

    std::vector<std::string> words = {"Hello", " ", "World"};
    std::string concat = std::accumulate(words.begin(), words.end(), std::string(),
         { return a + b; });
    std::cout << concat << "\n";  // "Hello World"
    return 0;
}

accumulate로 평균, 분산 계산

#include <numeric>
#include <vector>
#include <cmath>

double mean(const std::vector<double>& vec) {
    if (vec.empty()) return 0;
    double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
    return sum / vec.size();
}

double variance(const std::vector<double>& vec) {
    if (vec.size() < 2) return 0;
    double m = mean(vec);
    double sq_diff = std::accumulate(vec.begin(), vec.end(), 0.0,
        [m](double a, double x) { return a + (x - m) * (x - m); });
    return sq_diff / (vec.size() - 1);
}

accumulate vs reduce (C++17)

std::reduce는 병렬 실행을 고려한 집계이며, 연산 순서가 보장되지 않습니다. 부동소수점 합산에서 accumulate와 결과가 다를 수 있지만, 병렬화 시 성능이 좋습니다. 순서가 중요한 경우(예: 문자열 연결)에는 accumulate를 사용하세요.

#include <numeric>
#include <vector>
#include <execution>

int main() {
    std::vector<int> vec(1000000, 1);

    // 순차, 순서 보장
    int sum1 = std::accumulate(vec.begin(), vec.end(), 0);

    // 병렬 가능, 순서 비보장 (C++17)
    int sum2 = std::reduce(std::execution::par, vec.begin(), vec.end(), 0);
}

6. 완전한 STL 알고리즘 예제

예제 1: 로그 분석기 (정렬 + 검색 + 집계)

로그 분석기는 정렬·검색·집계를 모두 활용하는 대표적인 예제입니다. 타임스탬프 정렬, 레벨별 개수 집계, 조건 필터링을 STL 알고리즘으로 처리합니다.

// g++ -std=c++17 -o log_analyzer log_analyzer.cpp && ./log_analyzer
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iostream>

struct LogEntry {
    std::string timestamp;
    int level;  // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR
    std::string message;
};

int main() {
    std::vector<LogEntry> logs = {
        {"2026-04-23 10:00:00", 2, "Connection timeout"},
        {"2026-04-23 09:55:00", 1, "User login"},
        {"2026-04-23 10:01:00", 3, "Disk full"},
        {"2026-04-23 09:58:00", 1, "Request received"},
        {"2026-04-23 10:00:30", 2, "Retry attempt"}
    };

    // 1. 타임스탬프 오름차순 정렬
    std::sort(logs.begin(), logs.end(),
         {
            return a.timestamp < b.timestamp;
        });

    // 2. ERROR(3) 개수
    int error_count = std::count_if(logs.begin(), logs.end(),
         { return e.level == 3; });
    std::cout << "Errors: " << error_count << "\n";

    // 3. WARN 이상만 필터링 (copy_if)
    std::vector<LogEntry> critical;
    std::copy_if(logs.begin(), logs.end(), std::back_inserter(critical),
         { return e.level >= 2; });

    // 4. level 합계 (평균 레벨 계산용)
    int level_sum = std::accumulate(logs.begin(), logs.end(), 0,
         { return a + e.level; });
    double avg_level = static_cast<double>(level_sum) / logs.size();
    std::cout << "Avg level: " << avg_level << "\n";

    return 0;
}

실행 결과:

Errors: 1
Avg level: 1.8
#include <algorithm>
#include <numeric>
#include <vector>
#include <cmath>
#include <iostream>

int main() {
    std::vector<int> scores = {72, 85, 91, 68, 78, 88, 95, 62};

    // 1. 정규화 (0-100 -> 0-1)
    std::vector<double> normalized(scores.size());
    std::transform(scores.begin(), scores.end(), normalized.begin(),
         { return s / 100.0; });

    // 2. 정렬 (상위 5명)
    std::partial_sort(scores.begin(), scores.begin() + 5, scores.end(),
                     std::greater<int>());

    // 3. 80점 이상인지 이진 검색 (정렬 후)
    std::sort(scores.begin(), scores.end());
    bool has_high = std::binary_search(scores.begin(), scores.end(), 80,
         { return a < b; });  // 기본이지만 명시

    // 4. 평균
    double avg = std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
    std::cout << "Average: " << avg << "\n";

    return 0;
}

예제 3: 두 벡터 결합 및 집계

#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> prices = {100, 200, 150, 300};
    std::vector<int> quantities = {2, 1, 3, 2};

    // 각 품목별 금액 = price * quantity
    std::vector<int> amounts(prices.size());
    std::transform(prices.begin(), prices.end(), quantities.begin(),
                  amounts.begin(),  { return p * q; });
    // amounts: {200, 200, 450, 600}

    // 총 금액
    int total = std::accumulate(amounts.begin(), amounts.end(), 0);
    std::cout << "Total: " << total << "\n";  // 1450

    return 0;
}

7. 자주 발생하는 에러와 해결법

에러 1: binary_search를 정렬되지 않은 범위에 사용

증상: 검색 결과가 잘못되거나 예측 불가능합니다.

원인: binary_search, lower_bound, upper_bound정렬된 범위에서만 올바르게 동작합니다.

// ❌ 잘못된 사용
std::vector<int> vec = {5, 2, 8, 1, 9};
bool found = std::binary_search(vec.begin(), vec.end(), 5);  // UB!

// ✅ 올바른 사용
std::sort(vec.begin(), vec.end());
bool found = std::binary_search(vec.begin(), vec.end(), 5);

에러 2: transform 출력 범위 크기 부족

증상: Segmentation fault 또는 메모리 손상.

원인: 출력 범위가 입력보다 작으면 범위를 벗어나 씁니다.

// ❌ 잘못된 사용
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> result(2);  // 크기 부족!
std::transform(vec.begin(), vec.end(), result.begin(),  { return x * 2; });

// ✅ 올바른 사용
std::vector<int> result(vec.size());
std::transform(vec.begin(), vec.end(), result.begin(),  { return x * 2; });

// ✅ 또는 back_inserter (크기 미리 모름)
std::vector<int> result;
result.reserve(vec.size());
std::transform(vec.begin(), vec.end(), std::back_inserter(result),
               { return x * 2; });

에러 3: accumulate 초기값 타입 불일치

증상: 정수 합계가 잘못되거나, 부동소수점 정밀도 문제.

원인: std::accumulate(vec.begin(), vec.end(), 0)에서 0은 int이므로, double 벡터를 합할 때 매 단계 int로 변환되어 정밀도가 떨어집니다.

// ❌ 잘못된 사용 (double 벡터에 int 초기값)
std::vector<double> vec = {0.1, 0.2, 0.3};
double sum = std::accumulate(vec.begin(), vec.end(), 0);  // int 0 -> 부정확

// ✅ 올바른 사용
double sum = std::accumulate(vec.begin(), vec.end(), 0.0);  // 0.0

에러 4: 비교자에서 strict weak ordering 위반

증상: std::sort 호출 시 크래시 또는 무한 루프.

원인: 비교 함수가 a < a가 false, a < bb < a가 동시에 true가 되면 안 되며, a < bb < c이면 a < c여야 합니다.

// ❌ 잘못된 비교자 (a == b일 때 true 반환 가능)
std::sort(vec.begin(), vec.end(),
     { return a <= b; });  // a <= a -> true (위반)

// ✅ 올바른 비교자 (< 만 사용)
std::sort(vec.begin(), vec.end(),
     { return a < b; });

에러 5: 반복자 무효화 후 사용

증상: Segmentation fault 또는 예측 불가능한 동작.

원인: vec.push_back 등으로 재할당이 일어나면 기존 반복자가 무효화됩니다.

// ❌ 위험한 패턴
auto it = vec.begin();
vec.push_back(100);  // 재할당 가능 -> it 무효화
*it;  // UB

// ✅ 반복자 사용 직후에만 사용하거나, 인덱스 사용
size_t idx = std::distance(vec.begin(), it);
vec.push_back(100);
// idx는 여전히 유효 (단, 삽입 위치에 따라 의미 변경됨)

에러 6: empty 범위에서 accumulate

증상: 초기값이 그대로 반환됩니다. 에러는 아니지만, vec.empty()일 때 accumulate 결과가 초기값이므로 나눗셈 등에서 0으로 나누기 주의.

// ✅ empty 체크
double avg = vec.empty()
    ? 0.0
    : std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size();

8. 성능 최적화 팁

팁 1: 정렬이 필요할 때만 정렬

binary_search를 쓰려면 정렬이 필요하지만, 한 번만 검색할 거면 std::find(O(n))가 더 나을 수 있습니다. n이 작을 때는 정렬 비용 O(n log n)이 검색 비용 O(n)보다 클 수 있습니다.

// 한 번만 검색 -> find가 나을 수 있음
auto it = std::find(vec.begin(), vec.end(), target);

// 여러 번 검색 -> 정렬 후 binary_search
std::sort(vec.begin(), vec.end());
for (int key : keys) {
    if (std::binary_search(vec.begin(), vec.end(), key)) { /* ... */ }
}

팁 2: reserve로 재할당 줄이기

transform + back_inserter 또는 copy_if + back_inserter 사용 시, 결과 크기를 대략 알 수 있으면 reserve로 재할당을 줄입니다.

std::vector<int> result;
result.reserve(vec.size());  // 최대 vec.size()개
std::copy_if(vec.begin(), vec.end(), std::back_inserter(result),
              { return x % 2 == 0; });

팁 3: partial_sort로 상위 k개만

전체 정렬이 필요 없으면 partial_sortsort보다 빠릅니다. k가 작을수록 유리합니다.

// 상위 10개만 필요
std::partial_sort(vec.begin(), vec.begin() + 10, vec.end(), std::greater<int>());

팁 4: 커스텀 비교자 인라인

람다 비교자는 컴파일러가 인라인하기 쉽습니다. std::function을 넘기면 간접 호출 비용이 생깁니다.

// ✅ 람다 (인라인 가능)
std::sort(vec.begin(), vec.end(),  { return a < b; });

// ❌ std::function (간접 호출)
std::function<bool(int,int)> cmp =  { return a < b; };
std::sort(vec.begin(), vec.end(), cmp);

팁 5: std::reduce로 병렬 집계 (C++17)

대용량 데이터 집계 시 std::reducestd::execution::par로 병렬화할 수 있습니다. 연산 순서가 중요하지 않을 때만 사용하세요.

#include <numeric>
#include <execution>

int sum = std::reduce(std::execution::par, vec.begin(), vec.end(), 0);

성능 비교 요약

연산알고리즘복잡도비고
정렬sortO(n log n)일반적
정렬stable_sortO(n log n)순서 유지 필요 시
부분 정렬partial_sortO(n log k)상위 k개만
선형 검색findO(n)정렬 불필요
이진 검색binary_searchO(log n)정렬 필수
변환transformO(n)한 번 순회
집계accumulateO(n)순차
집계reduce (par)O(n/p)병렬

9. 프로덕션 패턴

패턴 1: erase-remove idiom

조건에 맞는 원소를 제거할 때 remove_if + erase 조합을 사용합니다.

// 짝수 제거
vec.erase(std::remove_if(vec.begin(), vec.end(),
                          { return x % 2 == 0; }),
          vec.end());

패턴 2: 정렬 + unique로 중복 제거

std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());

패턴 3: 파이프라인 체이닝

여러 알고리즘을 순차 적용해 데이터 파이프라인을 구성합니다.

// 1. 필터링
std::vector<int> filtered;
std::copy_if(vec.begin(), vec.end(), std::back_inserter(filtered),
              { return x > 0; });

// 2. 변환
std::vector<double> transformed(filtered.size());
std::transform(filtered.begin(), filtered.end(), transformed.begin(),
                { return std::sqrt(static_cast<double>(x)); });

// 3. 집계
double sum = std::accumulate(transformed.begin(), transformed.end(), 0.0);

패턴 4: 안전한 이진 검색 래퍼

정렬 여부를 확인한 뒤 binary_search를 호출하는 래퍼입니다.

template <typename Container, typename T>
bool safe_binary_search(const Container& c, const T& value) {
    if (!std::is_sorted(c.begin(), c.end()))
        return false;  // 또는 정렬 후 검색
    return std::binary_search(c.begin(), c.end(), value);
}

패턴 5: 범위 기반 알고리즘 적용 흐름

sequenceDiagram
    participant Input as 입력 데이터
    participant Filter as copy_if
    participant Transform as transform
    participant Sort as sort
    participant Aggregate as accumulate

    Input->>Filter: 원본 벡터
    Filter->>Transform: 필터링된 벡터
    Transform->>Sort: 변환된 벡터
    Sort->>Aggregate: 정렬된 벡터
    Aggregate->>Aggregate: 집계 결과

패턴 6: 알고리즘 선택 체크리스트

요구사항추천 알고리즘
전체 정렬sort
같은 값 순서 유지stable_sort
상위 k개만partial_sort
존재 여부 (정렬됨)binary_search
위치/개수 (정렬됨)lower_bound, upper_bound, equal_range
각 원소 변환transform
조건 만족 개수count_if
합/곱/연결accumulate
조건 제거remove_if + erase
중복 제거sort + unique + erase

패턴 7: 구현 체크리스트

프로덕션에 STL 알고리즘을 적용할 때 확인할 항목입니다.

  • binary_search 계열 사용 전 범위가 정렬되어 있는지 확인
  • transform 출력 범위 크기가 입력 이상인지 확인 (또는 back_inserter + reserve)
  • accumulate 초기값 타입이 집계 결과와 일치하는지 확인 (double이면 0.0)
  • 커스텀 비교자가 strict weak ordering을 만족하는지 확인
  • remove/remove_if 후 반드시 erase로 논리적 끝 구간 제거
  • 대용량 데이터 시 reserve로 재할당 최소화
  • 여러 번 검색 시 정렬 비용 대비 이진 검색 이득이 있는지 검토

정리

항목설명
정렬sort, stable_sort, partial_sort - 상황에 맞게 선택
검색binary_search, lower_bound, upper_bound - 정렬 필수
변환transform - 단항/이항, 출력 범위 크기 확인
집계accumulate - 초기값 타입 일치, reduce로 병렬화
에러정렬 없이 binary_search, 출력 범위 부족, 비교자 위반
성능reserve, partial_sort, 람다 비교자
프로덕션erase-remove, sort+unique, 파이프라인

핵심 원칙:

  1. 정렬된 범위에서만 binary_search 계열 사용
  2. transform 출력 범위 크기 확보
  3. accumulate 초기값 타입 일치 (0.0 for double)
  4. 비교자는 strict weak ordering 준수
  5. reserve로 불필요한 재할당 방지

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 데이터 처리, 정렬, 변환, 집계 등 일상적인 프로그래밍 작업에 필수입니다. 본문의 문제 시나리오와 프로덕션 패턴을 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.

한 줄 요약: sort·transform·accumulate·binary_search를 마스터하고, 흔한 에러를 피하며 프로덕션 패턴을 적용할 수 있습니다.


관련 글

  • C++ 알고리즘 선택 완벽 가이드 | 상황별 STL·커스텀 알고리즘 선택법 [#54-1]
  • C++ STL 알고리즘 기초 완벽 가이드 | sort·find
  • C++ 알고리즘 |
  • C++ Execution Policy |
  • C++ 병렬 알고리즘 완벽 가이드 | std::execution::par·par_unseq
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3