[2026] C++ STL 알고리즘 기초 완벽 가이드 | sort·find
이 글의 핵심
C++ STL 알고리즘 기초 입니다. sort, find, transform, accumulate 등 핵심 알고리즘의 사용법과 성능 특성을 실전 예제로 설명합니다. 데이터 처리 코드를 작성할 때마다 직접 for문을 돌리다 보면 이런 일이 반복됩니다: 인덱스 범위를 잘못 써서 Segmentation fault가 나요. 정렬할 때 같은 값 처리 로직을 빼먹었어요.
들어가며: “for문 작성하다 보니 버그가 자꾸 생겨요”
실제 겪는 문제 시나리오
데이터 처리 코드를 작성할 때마다 직접 for문을 돌리다 보면 이런 일이 반복됩니다: 아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
"인덱스 범위를 잘못 써서 Segmentation fault가 나요."
"정렬할 때 같은 값 처리 로직을 빼먹었어요."
"값을 찾을 때 존재하지 않으면 end() 체크를 깜빡했어요."
"조건에 맞는 원소를 제거하려다 반복자 무효화로 크래시가 났어요."
"합계·평균 계산할 때 초기값 타입을 잘못 넣어서 부동소수점 오차가 나요."
STL 알고리즘으로 해결:
| 문제 | STL 해결 |
|---|---|
| 인덱스 오류 | [begin, end) 반복자로 범위 명시, 경계 검증된 구현 사용 |
| 정렬 실수 | std::sort, std::stable_sort — 검증된 O(n log n) 구현 |
| 검색 실수 | std::find, std::find_if — end 체크 패턴 통일 |
| 개수 세기 | std::count, std::count_if — 한 줄로 집계 |
| 변환 실수 | std::transform — 선언적 변환, 출력 범위만 확보 |
| 복사 실수 | std::copy, std::copy_if — 범위 기반 복사 |
| 제거 실수 | std::remove + erase — erase-remove idiom |
| 요구 환경: C++17 이상 권장 | |
| 이 글을 읽으면: |
- sort, find, count, transform, accumulate, copy, remove를 완전히 이해할 수 있습니다.
- 문제 상황별 적절한 알고리즘을 선택할 수 있습니다.
- 흔한 에러를 피하고 베스트 프랙티스를 적용할 수 있습니다.
- 프로덕션 수준의 패턴을 사용할 수 있습니다.
실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
목차
- 문제 시나리오 상세
- 정렬: sort
- 검색: find
- 개수: count
- 변환: transform
- 집계: accumulate
- 복사: copy
- 제거: remove
- 완전한 예제 모음
- 자주 발생하는 에러와 해결법
- 베스트 프랙티스
- 프로덕션 패턴
- 구현 체크리스트
1. 문제 시나리오 상세
시나리오 1: 대량 데이터 정렬 시 성능·정확성 문제
상황: 수십만 건의 로그를 타임스탬프 기준으로 정렬해야 합니다. 직접 버블 정렬을 구현하면 O(n²)로 시간 초과가 발생하고, 같은 값의 순서를 잘못 처리하기 쉽습니다.
해결: std::sort는 인트로소트(퀵소트 + 힙소트 + 삽입정렬)로 O(n log n)에 정렬합니다. 같은 값의 순서가 중요하면 std::stable_sort를 사용합니다.
시나리오 2: 컨테이너에서 값 검색 시 end 체크 누락
상황: ID 목록에서 특정 ID가 있는지 확인하고, 있으면 인덱스를 사용합니다. for문으로 돌리다 vec[i] == target 체크 후 i를 사용하는데, 없을 때 -1이나 size()를 반환하는 로직을 빼먹기 쉽습니다.
해결: std::find는 “찾으면 반복자, 없으면 end”를 반환합니다. it != vec.end()로 존재 여부를 일관되게 확인할 수 있습니다.
시나리오 3: 조건 만족 개수 세기
상황: “에러 로그가 몇 개인지”, “80점 이상이 몇 명인지” 등을 세어야 합니다. 수동 카운터를 쓰면 초기화를 빼먹거나, 다른 변수와 섞이기 쉽습니다.
해결: std::count, std::count_if로 한 줄에 집계합니다.
시나리오 4: 데이터 변환 파이프라인
상황: JSON 파싱 결과를 DTO로 변환하거나, 센서 값을 정규화하는 등 “각 원소에 함수 적용”이 반복됩니다. for문은 가독성이 떨어지고 인덱스 실수가 납니다.
해결: std::transform으로 변환 로직을 선언적으로 표현합니다.
시나리오 5: 합계·평균·곱셈 등 집계
상황: 벡터의 합, 곱, 또는 문자열 리스트를 하나로 연결하는 “접기(fold)” 연산이 필요합니다. 수동 루프는 초기값 처리나 타입 변환에서 실수하기 쉽습니다.
해결: std::accumulate로 초기값부터 순차적으로 이항 함수를 적용해 집계합니다.
시나리오 6: 조건에 맞는 원소만 복사
상황: “양수만”, “에러 레벨만” 등 조건을 만족하는 원소만 새 컨테이너로 복사해야 합니다. for문으로 push_back하다 범위 오류가 나기 쉽습니다.
해결: std::copy_if + std::back_inserter로 한 줄에 처리합니다.
시나리오 7: 조건에 맞는 원소 제거
상황: “짝수 제거”, “만료된 항목 제거” 등입니다. 반복문 안에서 erase를 호출하면 반복자 무효화로 크래시가 납니다.
해결: std::remove_if + vec.erase 조합(erase-remove idiom)으로 안전하게 제거합니다.
STL 알고리즘 분류 다이어그램
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TB
subgraph sort[정렬]
S1[sort]
S2[stable_sort]
S3[partial_sort]
end
subgraph search[검색]
F1[find]
F2[find_if]
F3[find_if_not]
end
subgraph count[개수]
C1[count]
C2[count_if]
end
subgraph transform[변환]
T1[transform]
end
subgraph agg[집계]
A1[accumulate]
end
subgraph copy[복사]
CP1[copy]
CP2[copy_if]
end
subgraph remove[제거]
R1[remove]
R2[remove_if]
end
input["[begin, end) 반복자"] --> sort
input --> search
input --> count
input --> transform
input --> agg
input --> copy
input --> remove
2. 정렬: sort
std::sort: 기본 정렬
std::sort는 반개구간 [begin, end) 에 있는 원소를 기본적으로 오름차순으로 정렬합니다. 내부적으로 인트로소트를 사용하며, 비교 연산만 정의되어 있으면 어떤 타입이든 정렬할 수 있습니다.
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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”를 의미합니다.
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
});
return 0;
}
stable_sort: 안정 정렬
같은 값끼리 원래 입력 순서를 유지해야 할 때 사용합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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) - 같은 점수 내 순서 유지
return 0;
}
partial_sort: 상위 k개만 정렬
전체를 정렬할 필요 없이 “상위 N개만 올바른 순서”가 필요할 때 사용합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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>());
for (int i = 0; i < 3; ++i)
std::cout << vec[i] << " "; // 9 8 7
std::cout << "\n";
return 0;
}
3. 검색: find
std::find: 값으로 검색
std::find는 선형 검색으로 범위 내에서 value와 같은 첫 번째 원소의 반복자를 반환합니다. 없으면 end를 반환합니다. O(n)이지만 정렬이 필요 없습니다.
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
auto it = std::find(vec.begin(), vec.end(), 8);
if (it != vec.end()) {
std::cout << "Found at index: " << std::distance(vec.begin(), it) << "\n"; // 2
} else {
std::cout << "Not found\n";
}
// 없는 값
auto it2 = std::find(vec.begin(), vec.end(), 100);
if (it2 == vec.end()) {
std::cout << "100 not found\n";
}
return 0;
}
std::find_if: 조건으로 검색
조건(술어)을 만족하는 첫 번째 원소의 반복자를 반환합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
// 5보다 큰 첫 번째 원소
auto it = std::find_if(vec.begin(), vec.end(),
{ return x > 5; });
if (it != vec.end()) {
std::cout << "First > 5: " << *it << "\n"; // 8
}
// 짝수인 첫 번째 원소
auto it2 = std::find_if(vec.begin(), vec.end(),
{ return x % 2 == 0; });
if (it2 != vec.end()) {
std::cout << "First even: " << *it2 << "\n"; // 2
}
return 0;
}
std::find_if_not: 조건을 만족하지 않는 첫 원소
조건을 만족하지 않는 첫 번째 원소의 반복자를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
int main() {
std::vector<int> vec = {2, 4, 6, 7, 8, 10};
// 홀수인 첫 번째 원소
auto it = std::find_if_not(vec.begin(), vec.end(),
{ return x % 2 == 0; });
if (it != vec.end()) {
// *it == 7
}
return 0;
}
4. 개수: count
std::count: 값과 같은 원소 개수
범위 내에서 value와 같은 원소의 개수를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 2, 2, 3, 4, 2, 5};
size_t n = std::count(vec.begin(), vec.end(), 2);
std::cout << "Count of 2: " << n << "\n"; // 4
n = std::count(vec.begin(), vec.end(), 10);
std::cout << "Count of 10: " << n << "\n"; // 0
return 0;
}
std::count_if: 조건 만족 개수
조건(술어)을 만족하는 원소의 개수를 반환합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
// 5보다 큰 원소 개수
size_t n = std::count_if(vec.begin(), vec.end(),
{ return x > 5; });
std::cout << "Count > 5: " << n << "\n"; // 3
// 짝수 개수
n = std::count_if(vec.begin(), vec.end(),
{ return x % 2 == 0; });
std::cout << "Even count: " << n << "\n"; // 2
return 0;
}
5. 변환: transform
std::transform: 단항 변환
범위의 각 원소에 함수(람다)를 적용한 결과를 출력 범위에 씁니다. 출력을 입력과 같은 범위로 주면 제자리(in-place) 변환이 됩니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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: 두 범위 결합 (이항 변환)
두 시퀀스의 같은 위치 원소를 한 번에 받아 결과를 출력합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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;
}
6. 집계: accumulate
std::accumulate: 기본 동작
std::accumulate는 왼쪽부터 차례로 접어 나가는(fold) 연산입니다. 세 번째 인자는 초기값이고, 기본 동작은 초기값 + vec[0] + vec[1] + ...입니다. <numeric> 헤더가 필요합니다.
아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
flowchart LR
I["초기값 0"] --> A1["+ vec[0]"]
A1 --> A2["+ vec[1]"]
A2 --> A3["+ vec[2]"]
A3 --> R["최종 결과"]
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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로 평균, 분산 계산
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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);
}
7. 복사: copy
std::copy: 전체 복사
[first, last) 범위의 원소를 출력 반복자로 복사합니다.
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(src.size());
std::copy(src.begin(), src.end(), dst.begin());
// dst: {1, 2, 3, 4, 5}
// back_inserter로 동적 추가
std::vector<int> dst2;
dst2.reserve(src.size());
std::copy(src.begin(), src.end(), std::back_inserter(dst2));
return 0;
}
std::copy_if: 조건 만족 원소만 복사
조건(술어)을 만족하는 원소만 출력 범위로 복사합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> even;
std::copy_if(src.begin(), src.end(), std::back_inserter(even),
{ return x % 2 == 0; });
// even: {2, 4, 6, 8, 10}
// reserve로 재할당 최소화
std::vector<int> result;
result.reserve(src.size());
std::copy_if(src.begin(), src.end(), std::back_inserter(result),
{ return x > 5; });
// result: {6, 7, 8, 9, 10}
return 0;
}
std::copy_n: n개만 복사
처음 n개 원소만 복사합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(3);
std::copy_n(src.begin(), 3, dst.begin());
// dst: {1, 2, 3}
return 0;
}
8. 제거: remove
remove의 동작 방식 (주의)
std::remove와 std::remove_if는 실제로 원소를 삭제하지 않습니다. 제거할 값들을 뒤로 밀어내고, “새 논리적 끝” 반복자를 반환합니다. 물리적 크기는 그대로이므로, 반드시 erase와 함께 사용해야 합니다.
아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
flowchart LR
subgraph before[remove 전]
B1[1] --> B2[2] --> B3[2] --> B4[3] --> B5[2]
end
subgraph after["remove 후 (erase 전)"]
A1[1] --> A2[3] --> A3[?] --> A4[?] --> A5[?]
end
erase-remove idiom
조건에 맞는 원소를 실제로 제거하려면 remove_if의 반환값을 erase에 넘깁니다.
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
// 값 2 제거
vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
// vec: {1, 3, 4, 5}
// 짝수 제거
std::vector<int> vec2 = {1, 2, 3, 4, 5, 6};
vec2.erase(std::remove_if(vec2.begin(), vec2.end(),
{ return x % 2 == 0; }), vec2.end());
// vec2: {1, 3, 5}
return 0;
}
std::remove: 값으로 제거
지정한 값과 같은 모든 원소를 “제거”하고, 새 끝 반복자를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 2, 3, 2, 4};
auto new_end = std::remove(vec.begin(), vec.end(), 2);
vec.erase(new_end, vec.end());
// vec: {1, 3, 4}
return 0;
}
std::remove_if: 조건으로 제거
조건을 만족하는 모든 원소를 제거합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 5보다 큰 원소 제거
vec.erase(std::remove_if(vec.begin(), vec.end(),
{ return x > 5; }), vec.end());
// vec: {1, 2, 3, 4, 5}
return 0;
}
9. 완전한 예제 모음
예제 1: 로그 분석기 (sort + find + count + copy + accumulate)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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. sort: 타임스탬프 오름차순 정렬
std::sort(logs.begin(), logs.end(),
{
return a.timestamp < b.timestamp;
});
// 2. count_if: ERROR(3) 개수
int error_count = std::count_if(logs.begin(), logs.end(),
{ return e.level == 3; });
std::cout << "Errors: " << error_count << "\n";
// 3. copy_if: WARN 이상만 필터링
std::vector<LogEntry> critical;
std::copy_if(logs.begin(), logs.end(), std::back_inserter(critical),
{ return e.level >= 2; });
// 4. find_if: 첫 ERROR 찾기
auto it = std::find_if(logs.begin(), logs.end(),
{ return e.level == 3; });
if (it != logs.end()) {
std::cout << "First error: " << it->message << "\n";
}
// 5. accumulate: 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
First error: Disk full
Avg level: 1.8
예제 2: 점수 처리 파이프라인 (transform + sort + find + remove)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> scores = {72, 85, 91, 68, 78, 88, 95, 62, -1, 0};
// 1. remove_if: 유효하지 않은 점수(-1, 0) 제거
scores.erase(std::remove_if(scores.begin(), scores.end(),
{ return s <= 0; }), scores.end());
// 2. transform: 정규화 (0-100 -> 0.0-1.0)
std::vector<double> normalized(scores.size());
std::transform(scores.begin(), scores.end(), normalized.begin(),
{ return s / 100.0; });
// 3. sort: 상위 5명 (partial_sort)
std::partial_sort(scores.begin(), scores.begin() + 5, scores.end(),
std::greater<int>());
// 4. find: 80점인 사람이 있는지
auto it = std::find(scores.begin(), scores.end(), 80);
bool has_80 = (it != scores.end());
// 5. accumulate: 평균
double avg = std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
std::cout << "Average: " << avg << "\n";
return 0;
}
예제 3: 두 벡터 결합 및 집계 (transform + accumulate)
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#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};
// transform: 각 품목별 금액 = 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}
// accumulate: 총 금액
int total = std::accumulate(amounts.begin(), amounts.end(), 0);
std::cout << "Total: " << total << "\n"; // 1450
return 0;
}
10. 자주 발생하는 에러와 해결법
에러 1: find 결과를 end 체크 없이 사용
증상: 찾지 못했을 때 *it 접근으로 Segmentation fault 또는 undefined behavior.
원인: std::find가 end를 반환했는데, 존재한다고 가정하고 역참조함.
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 잘못된 사용
auto it = std::find(vec.begin(), vec.end(), 100);
int value = *it; // 100이 없으면 UB!
// ✅ 올바른 사용
auto it = std::find(vec.begin(), vec.end(), 100);
if (it != vec.end()) {
int value = *it;
} else {
// 없을 때 처리
}
에러 2: transform 출력 범위 크기 부족
증상: Segmentation fault 또는 메모리 손상.
원인: 출력 범위가 입력보다 작으면 범위를 벗어나 씁니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 잘못된 사용
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 + reserve
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로 변환됩니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 잘못된 사용 (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: remove만 하고 erase 안 함
증상: 벡터 크기는 그대로인데, 뒤쪽에 “쓰레기” 값이 남아 있음.
원인: remove/remove_if는 원소를 삭제하지 않고, 새 논리적 끝만 반환합니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 잘못된 사용
std::vector<int> vec = {1, 2, 2, 3};
std::remove(vec.begin(), vec.end(), 2); // vec 크기 그대로, 뒤에 쓰레기
// ✅ 올바른 사용 (erase-remove idiom)
vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
에러 5: 비교자에서 strict weak ordering 위반
증상: std::sort 호출 시 크래시 또는 무한 루프.
원인: 비교 함수가 a < a가 false, a <= b처럼 <=를 쓰면 a == b일 때 true가 되어 위반.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 잘못된 비교자
std::sort(vec.begin(), vec.end(),
{ return a <= b; }); // a <= a -> true (위반)
// ✅ 올바른 비교자 (< 만 사용)
std::sort(vec.begin(), vec.end(),
{ return a < b; });
에러 6: copy_if에서 출력 범위 reserve 누락
증상: 대량 데이터에서 copy_if + back_inserter 사용 시 재할당이 반복되어 느려짐.
해결: 결과 크기를 대략 알 수 있으면 reserve로 재할당을 줄입니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ reserve로 재할당 최소화
std::vector<int> result;
result.reserve(src.size()); // 최대 src.size()개
std::copy_if(src.begin(), src.end(), std::back_inserter(result),
{ return x % 2 == 0; });
에러 7: empty 범위에서 accumulate 후 나눗셈
증상: vec.empty()일 때 accumulate 결과가 초기값인데, vec.size()로 나누면 0으로 나누기.
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ empty 체크
double avg = vec.empty()
? 0.0
: std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size();
11. 베스트 프랙티스
1. for문 대신 알고리즘 우선
반복문으로 “찾기, 세기, 복사, 제거”를 직접 구현하기 전에, STL에 해당 알고리즘이 있는지 확인합니다. 가독성과 안정성이 좋아집니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 수동 루프
int count = 0;
for (size_t i = 0; i < vec.size(); ++i) {
if (vec[i] > 5) ++count;
}
// ✅ count_if
int count = std::count_if(vec.begin(), vec.end(), { return x > 5; });
2. 람다 비교자 사용 (std::function 피하기)
std::function을 넘기면 간접 호출 비용이 생깁니다. 람다는 컴파일러가 인라인하기 쉽습니다.
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ 람다 (인라인 가능)
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);
3. reserve로 재할당 최소화
back_inserter와 함께 쓸 때, 결과 크기를 대략 알 수 있으면 reserve를 호출합니다.
다음은 간단한 cpp 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> result;
result.reserve(vec.size());
std::copy_if(vec.begin(), vec.end(), std::back_inserter(result),
{ return x > 0; });
4. 정렬이 필요할 때만 정렬
한 번만 검색할 거면 std::find(O(n))가 나을 수 있습니다. 여러 번 검색할 때만 정렬 후 binary_search를 고려합니다.
5. partial_sort로 상위 k개만
전체 정렬이 필요 없으면 partial_sort가 sort보다 빠릅니다.
// 상위 10개만 필요
std::partial_sort(vec.begin(), vec.begin() + 10, vec.end(), std::greater<int>());
12. 프로덕션 패턴
패턴 1: erase-remove idiom
조건에 맞는 원소를 제거할 때 remove_if + erase 조합을 사용합니다.
다음은 간단한 cpp 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 짝수 제거
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: 파이프라인 체이닝
여러 알고리즘을 순차 적용해 데이터 파이프라인을 구성합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 1. copy_if: 필터링
std::vector<int> filtered;
std::copy_if(vec.begin(), vec.end(), std::back_inserter(filtered),
{ return x > 0; });
// 2. transform: 변환
std::vector<double> transformed(filtered.size());
std::transform(filtered.begin(), filtered.end(), transformed.begin(),
{ return std::sqrt(static_cast<double>(x)); });
// 3. accumulate: 집계
double sum = std::accumulate(transformed.begin(), transformed.end(), 0.0);
패턴 4: find + distance로 인덱스 얻기
아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
auto it = std::find(vec.begin(), vec.end(), target);
if (it != vec.end()) {
size_t index = std::distance(vec.begin(), it);
// index 사용
}
패턴 5: count_if로 조건 필터 개수
size_t error_count = std::count_if(logs.begin(), logs.end(),
{ return e.level >= 3; });
패턴 6: transform + accumulate로 가중 합
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> values = {1, 2, 3, 4, 5};
std::vector<int> weights = {1, 2, 1, 2, 1};
std::vector<int> weighted(values.size());
std::transform(values.begin(), values.end(), weights.begin(),
weighted.begin(), { return v * w; });
int total = std::accumulate(weighted.begin(), weighted.end(), 0);
성능 비교 요약
| 연산 | 알고리즘 | 복잡도 | 비고 |
|---|---|---|---|
| 정렬 | sort | O(n log n) | 일반적 |
| 정렬 | stable_sort | O(n log n) | 순서 유지 필요 시 |
| 부분 정렬 | partial_sort | O(n log k) | 상위 k개만 |
| 선형 검색 | find | O(n) | 정렬 불필요 |
| 개수 | count/count_if | O(n) | 한 번 순회 |
| 변환 | transform | O(n) | 한 번 순회 |
| 집계 | accumulate | O(n) | 순차 |
| 복사 | copy/copy_if | O(n) | 한 번 순회 |
| 제거 | remove + erase | O(n) | erase-remove |
13. 구현 체크리스트
프로덕션에 STL 알고리즘을 적용할 때 확인할 항목입니다.
-
find/find_if결과 사용 전it != end체크 -
transform출력 범위 크기가 입력 이상인지 확인 (또는back_inserter+reserve) -
accumulate초기값 타입이 집계 결과와 일치하는지 확인 (double이면0.0) - 커스텀 비교자가 strict weak ordering을 만족하는지 확인
-
remove/remove_if후 반드시erase로 논리적 끝 구간 제거 -
copy_if+back_inserter사용 시reserve로 재할당 최소화 - empty 범위에서 나눗셈 시 0으로 나누기 방지
정리
| 항목 | 설명 |
|---|---|
| 정렬 | sort, stable_sort, partial_sort — 상황에 맞게 선택 |
| 검색 | find, find_if, find_if_not — end 체크 필수 |
| 개수 | count, count_if — 한 줄 집계 |
| 변환 | transform — 단항/이항, 출력 범위 크기 확인 |
| 집계 | accumulate — 초기값 타입 일치 |
| 복사 | copy, copy_if — reserve로 재할당 최소화 |
| 제거 | remove + erase — erase-remove idiom |
| 에러 | end 체크, 출력 범위 부족, 비교자 위반, erase 누락 |
| 프로덕션 | erase-remove, sort+unique, 파이프라인 체이닝 |
| 핵심 원칙: |
- find 결과 사용 전 반드시 end 체크
- transform 출력 범위 크기 확보
- accumulate 초기값 타입 일치 (0.0 for double)
- remove/remove_if 후 반드시 erase
- reserve로 불필요한 재할당 방지
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 데이터 정렬, 검색, 집계, 변환, 복사, 제거 등 일상적인 컨테이너 처리에 필수입니다. for문 대신 STL 알고리즘을 쓰면 버그가 줄고 가독성이 올라갑니다.
Q. 선행으로 읽으면 좋은 글은?
A. STL 컨테이너 기초와 람다 표현식을 먼저 익히면 좋습니다.