C++ count·count_if와 all_of·any_of·none_of로 조건 집계하기
이 글의 핵심
직접 for 루프로 개수를 세던 코드를 표준 알고리즘으로 바꾸면 의도가 분명해지지만, 반환 타입이 difference_type이라는 점이나 빈 범위에서 all_of가 true를 돌려주는 동작은 헷갈리기 쉽습니다. any_of의 단락 평가와 성능 차이, 여러 번 순회하는 대신 한 번에 집계하는 방법까지 짚어 코딩 테스트와 실무에서 바로 쓸 수 있게 합니다.
들어가며
카운트 알고리즘은 컨테이너의 요소를 세거나 조건을 검사하는 알고리즘입니다. count, count_if, all_of, any_of, none_of 등을 제공합니다.
반복문 대신 쓰는 이유
int n = 0; for (auto x : v) if (x == 2) ++n;은 세 줄이지만 읽는 사람은 본문을 끝까지 봐야 “세는 코드”라는 것을 압니다. std::count(v.begin(), v.end(), 2)는 이름만으로 의도가 드러나고, 카운터 초기화를 빠뜨리거나 조건을 반대로 쓰는 실수가 끼어들 자리가 없습니다. all_of/any_of/none_of는 여기에 “조건을 만족하는 원소를 찾으면 바로 멈춘다”는 동작까지 이름에 담고 있어서, break를 어디에 넣을지 고민할 필요도 없습니다.
이 다섯 알고리즘은 모두 입력 반복자만 요구하므로 vector, list, map, 배열, 심지어 std::istream_iterator로 읽는 스트림에도 그대로 쓸 수 있습니다. 반대로 말하면 이 알고리즘들은 정렬 여부 같은 정보를 활용하지 않는 선형 탐색이라는 뜻이기도 합니다. 정렬된 데이터에서 특정 값의 개수를 세야 한다면 뒤에서 설명하는 equal_range가 훨씬 빠릅니다.
std::count로 특정 값 세기
벡터에서 값 개수 세기
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
// 2의 개수
int count = std::count(v.begin(), v.end(), 2);
std::cout << "2의 개수: " << count << std::endl; // 3
return 0;
}
문자열에서 문자 개수 세기
#include <algorithm>
#include <string>
#include <iostream>
int main() {
std::string text = "hello world";
// 'l'의 개수
int count = std::count(text.begin(), text.end(), 'l');
std::cout << "'l'의 개수: " << count << std::endl; // 3
return 0;
}
count는 각 원소를 value와 ==로 비교합니다. 그래서 직접 만든 구조체에 쓰려면 operator==가 정의되어 있어야 하고, 없으면 no match for 'operator==' 에러가 템플릿 내부 깊은 곳에서 길게 나옵니다. C++20이라면 bool operator==(const Student&) const = default; 한 줄로 해결됩니다.
문자열에서 쓸 때는 바이트 단위로 센다는 점을 기억해야 합니다. std::string은 UTF-8 바이트의 나열이라, std::count(text.begin(), text.end(), '가')는 컴파일 경고와 함께 엉뚱한 결과를 냅니다. ‘가’는 UTF-8로 3바이트라 char 하나에 담기지 않기 때문입니다. 한글이나 이모지를 글자 단위로 세려면 std::u32string으로 변환하거나 ICU 같은 유니코드 라이브러리가 필요합니다. 또 대소문자를 구분하지 않고 세려면 count 대신 count_if에 std::tolower를 적용한 람다를 넘겨야 합니다. 이때 std::tolower에 char를 바로 넘기면 음수 값(한글 바이트 등)에서 미정의 동작이 되므로 static_cast<unsigned char>(c)로 변환한 뒤 넘기는 것이 정석입니다.
std::set이나 std::map처럼 연관 컨테이너에서는 알고리즘 std::count 대신 멤버 함수 s.count(key)를 써야 합니다. 알고리즘 버전은 트리 전체를 O(n)으로 훑지만, 멤버 함수는 트리 구조를 이용해 O(log n)(해시 컨테이너는 평균 O(1))으로 끝냅니다. 존재 여부만 궁금하다면 C++20의 s.contains(key)가 의도를 더 분명히 보여 줍니다.
std::count_if로 조건을 만족하는 요소 세기
람다 조건으로 세기
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 짝수 개수
int evenCount = std::count_if(v.begin(), v.end(), [](int x) {
return x % 2 == 0;
});
std::cout << "짝수 개수: " << evenCount << std::endl; // 5
// 5보다 큰 수
int greaterThan5 = std::count_if(v.begin(), v.end(), [](int x) {
return x > 5;
});
std::cout << "5보다 큰 수: " << greaterThan5 << std::endl; // 5
return 0;
}
count_if의 세 번째 인자는 원소 하나를 받아 bool(또는 bool로 변환 가능한 값)을 돌려주는 단항 술어(unary predicate)입니다. 람다를 인라인으로 넘기면 컴파일러가 대부분 인라인 최적화해서 손으로 쓴 루프와 같은 성능이 나옵니다. 같은 조건을 여러 곳에서 쓴다면 auto isEven = [](int x) { return x % 2 == 0; };처럼 이름을 붙여 두면 가독성이 좋아집니다.
조건 람다를 쓸 때 흔히 걸리는 함정은 음수의 나머지입니다. C++에서 -3 % 2는 1이 아니라 -1이므로, 홀수 판별을 x % 2 == 1로 쓰면 음수 홀수를 놓칩니다. 이 글의 예제처럼 짝수 조건 x % 2 == 0이나 홀수 조건 x % 2 != 0을 쓰면 부호와 관계없이 올바르게 동작합니다.
구조체 멤버 조건으로 세기
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
struct Student {
std::string name;
int score;
};
int main() {
std::vector<Student> students = {
{"Alice", 85},
{"Bob", 92},
{"Charlie", 78},
{"David", 95},
{"Eve", 88}
};
// 90점 이상
int highScores = std::count_if(students.begin(), students.end(),
[](const Student& s) {
return s.score >= 90;
});
std::cout << "90점 이상: " << highScores << "명" << std::endl; // 2명
return 0;
}
구조체 원소는 람다에서 const Student&로 받는 것이 중요합니다. [](Student s)처럼 값으로 받으면 원소마다 std::string name이 복사되어, 학생 수가 많을수록 불필요한 메모리 할당이 쌓입니다. 이 코드는 결과를 int에 담았는데, 반환 타입은 사실 std::ptrdiff_t라 64비트 환경에서는 축소 변환 경고(-Wconversion)가 나올 수 있습니다. 아래 “count의 반환 타입은 부호 있는 difference_type” 항목에서 자세히 다룹니다.
기준 점수가 실행 중에 정해진다면 [threshold](const Student& s) { return s.score >= threshold; }처럼 캡처해서 씁니다. C++20 Ranges를 쓸 수 있다면 std::ranges::count_if(students, [](int s) { return s >= 90; }, &Student::score);처럼 프로젝션으로 필드를 지정해 람다를 더 단순하게 만들 수도 있고, 반복자 쌍 대신 컨테이너를 바로 넘길 수 있어 begin/end 짝을 잘못 맞추는 실수도 사라집니다.
all_of·any_of·none_of로 조건 검사하기
all_of: 모든 요소가 만족하는가
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v1 = {2, 4, 6, 8, 10};
std::vector<int> v2 = {2, 4, 5, 8, 10};
// 모두 짝수?
bool allEven1 = std::all_of(v1.begin(), v1.end(), [](int x) {
return x % 2 == 0;
});
bool allEven2 = std::all_of(v2.begin(), v2.end(), [](int x) {
return x % 2 == 0;
});
std::cout << "v1 모두 짝수: " << std::boolalpha << allEven1 << std::endl; // true
std::cout << "v2 모두 짝수: " << std::boolalpha << allEven2 << std::endl; // false
return 0;
}
all_of는 조건이 거짓인 원소를 처음 만나는 순간 false를 반환하고 멈춥니다. v2에서는 세 번째 원소 5에서 멈추므로 뒤의 8, 10은 검사하지 않습니다. 개념적으로 all_of는 std::find_if_not(first, last, p) == last와 같고, “조건을 어기는 원소가 하나도 없다”는 뜻입니다.
std::boolalpha는 bool을 1/0 대신 true/false로 출력하게 하는 조작자인데, 한 번 설정하면 스트림에 계속 남아 있습니다. 그래서 두 번째 줄의 std::boolalpha는 사실 필요 없고, 반대로 이후 코드에서 bool을 숫자로 출력하고 싶다면 std::noboolalpha로 되돌려야 합니다. 전역 std::cout의 상태를 바꾸는 코드는 다른 모듈의 출력에도 영향을 주므로, 라이브러리 코드에서는 조심해서 써야 합니다.
any_of: 하나라도 만족하는가
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v1 = {1, 3, 5, 7, 9};
std::vector<int> v2 = {1, 3, 4, 7, 9};
// 하나라도 짝수?
bool hasEven1 = std::any_of(v1.begin(), v1.end(), [](int x) {
return x % 2 == 0;
});
bool hasEven2 = std::any_of(v2.begin(), v2.end(), [](int x) {
return x % 2 == 0;
});
std::cout << "v1 짝수 있음: " << std::boolalpha << hasEven1 << std::endl; // false
std::cout << "v2 짝수 있음: " << std::boolalpha << hasEven2 << std::endl; // true
return 0;
}
none_of: 하나도 만족하지 않는가
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 3, 5, 7, 9};
// 음수 없음?
bool noNegative = std::none_of(v.begin(), v.end(), [](int x) {
return x < 0;
});
std::cout << "음수 없음: " << std::boolalpha << noNegative << std::endl; // true
return 0;
}
세 알고리즘은 서로 변환할 수 있습니다. none_of(p)는 !any_of(p)와 같고, all_of(p)는 none_of(!p)와 같습니다. 그런데도 세 가지를 모두 제공하는 이유는 읽기 쉬움 때문입니다. !std::any_of(..., [](int x) { return x < 0; })보다 std::none_of(..., [](int x) { return x < 0; })가 “음수가 없다”는 의도를 바로 보여 줍니다. 부정 연산자를 조건 안팎에 겹쳐 쓰면(!all_of(... !(x > 0))) 논리를 잘못 뒤집는 실수가 흔하므로, 의도에 맞는 이름을 고르는 것이 버그를 줄이는 방법입니다.
any_of는 “그런 원소가 있는가”만 알려 주고 어디에 있는지는 알려 주지 않습니다. 찾은 원소를 이어서 써야 한다면 any_of로 확인한 뒤 다시 find_if로 찾는 이중 순회 대신, 처음부터 auto it = std::find_if(...); if (it != v.end()) { ... }를 쓰는 것이 효율적입니다.
빈 범위·반환 타입·단락 평가의 함정
빈 범위에서 all_of가 true를 반환하는 이유
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> empty;
// count: 0
int count = std::count(empty.begin(), empty.end(), 5);
std::cout << "count: " << count << std::endl; // 0
// all_of: true (공허한 참)
bool all = std::all_of(empty.begin(), empty.end(), [](int x) { return x > 0; });
std::cout << "all_of: " << std::boolalpha << all << std::endl; // true
// any_of: false
bool any = std::any_of(empty.begin(), empty.end(), [](int x) { return x > 0; });
std::cout << "any_of: " << std::boolalpha << any << std::endl; // false
// none_of: true
bool none = std::none_of(empty.begin(), empty.end(), [](int x) { return x < 0; });
std::cout << "none_of: " << std::boolalpha << none << std::endl; // true
return 0;
}
빈 범위에서 all_of가 true인 것은 수학의 공허한 참(vacuous truth) 규칙을 따른 것입니다. “조건을 어기는 원소가 하나도 없다”는 명제는 원소가 없으면 자동으로 참이 됩니다. 논리적으로는 일관되지만, 실무 코드에서는 이 동작이 버그가 되는 경우가 많습니다. 예를 들어 “모든 테스트가 통과했으면 배포”를 all_of(results, passed)로 구현하면, 테스트 결과를 수집하는 단계가 실패해 목록이 비었을 때 아무것도 검사하지 않고 배포가 진행됩니다.
제가 검증 로직에서 가장 조심하는 부분도 이것입니다. “모두 만족”을 확인하는 코드는 거의 항상 “그리고 최소 하나는 있어야 한다”는 조건이 숨어 있으므로, !results.empty() && std::all_of(...)처럼 비어 있지 않음을 명시적으로 함께 검사하는 편이 안전합니다. 반대로 any_of가 빈 범위에서 false인 것은 직관과 맞아서 문제가 되는 경우가 적습니다.
count의 반환 타입은 부호 있는 difference_type
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// ✅ count 반환 타입: difference_type (보통 ptrdiff_t) 그대로 받기
auto count = std::count(v.begin(), v.end(), 3);
// ⚠️ 부호 없는 타입에 담으면 -Wsign-conversion 경고 대상
size_t count2 = static_cast<size_t>(std::count(v.begin(), v.end(), 3));
std::cout << "count: " << count << std::endl;
std::cout << "count2: " << count2 << std::endl;
return 0;
}
반환 타입이 size_t가 아니라 부호 있는 difference_type인 이유는, 표준 알고리즘이 개수를 “두 반복자 사이의 거리”와 같은 타입으로 표현하기 때문입니다. 개수가 음수가 될 수는 없지만 반복자 산술과 일관성을 맞춘 설계입니다. 이것이 실제 문제로 드러나는 곳은 비교입니다. if (std::count(...) == v.size())처럼 size()의 반환값(size_t)과 비교하면 comparison of integer expressions of different signedness 경고가 나옵니다. 값이 작을 때는 결과가 맞지만, 경고를 무시하는 습관이 들면 부호 없는 타입이 섞인 다른 곳에서 진짜 버그를 놓치게 됩니다.
실무 규칙은 단순합니다. 결과는 auto로 받고, size()와 비교해야 할 때는 C++20의 std::ssize(v)(부호 있는 크기)를 쓰거나 한쪽을 명시적으로 static_cast합니다. 이 글의 다른 예제들이 int에 담는 것은 설명을 짧게 하기 위한 것이고, 원소가 20억 개를 넘을 수 있는 데이터라면 int로는 값이 잘립니다.
단락 평가와 predicate 부작용
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int checks = 0;
// any_of: 첫 true에서 중단
bool hasEven = std::any_of(v.begin(), v.end(), [&checks](int x) {
++checks;
std::cout << "검사: " << x << std::endl;
return x % 2 == 0;
});
std::cout << "짝수 있음: " << std::boolalpha << hasEven << std::endl;
std::cout << "검사 횟수: " << checks << std::endl; // 2 (1, 2만 검사)
return 0;
}
출력:
검사: 1
검사: 2
짝수 있음: true
검사 횟수: 2
단락 평가 덕분에 any_of는 조건을 만족하는 원소가 앞쪽에 있을수록 빠릅니다. 최악의 경우(조건을 만족하는 원소가 없거나 맨 끝에 있을 때)에만 전체를 훑습니다. 반면 count_if는 항상 끝까지 순회하므로, “하나라도 있는가”를 확인하려고 count_if(...) > 0을 쓰는 것은 흔한 비효율입니다. 백만 개 원소 중 첫 번째가 조건을 만족해도 count_if는 백만 번 검사합니다.
이 예제는 단락 평가를 보여 주려고 술어에서 checks를 증가시키고 출력까지 했지만, 실무에서는 술어에 부수 효과를 두지 않는 것이 원칙입니다. 표준은 순차 버전에서 술어를 최대 last - first번 호출한다고만 규정하고, 병렬 실행 정책(std::execution::par)을 쓰면 호출 순서도 횟수도 달라집니다. 병렬 버전에서 이 람다처럼 checks를 참조로 증가시키면 데이터 경쟁이 됩니다.
조건마다 따로 순회할 때의 비용
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// ❌ 여러 번 순회
int even = std::count_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; });
int odd = std::count_if(v.begin(), v.end(), [](int x) { return x % 2 != 0; });
int gt5 = std::count_if(v.begin(), v.end(), [](int x) { return x > 5; });
// ✅ 한 번에 순회
int evenCount = 0, oddCount = 0, gt5Count = 0;
for (int x : v) {
if (x % 2 == 0) ++evenCount;
else ++oddCount;
if (x > 5) ++gt5Count;
}
std::cout << "짝수: " << evenCount << ", 홀수: " << oddCount
<< ", >5: " << gt5Count << std::endl;
return 0;
}
원소 10개에서는 차이가 없지만, 캐시에 다 들어가지 않는 큰 데이터(수백 MB)에서는 순회 횟수가 곧 메모리를 읽는 횟수입니다. 세 번의 count_if는 같은 메모리를 세 번 읽고, 한 번의 루프는 한 번만 읽습니다. 계산보다 메모리 대역폭이 병목인 경우가 많아서, 단순한 조건 여러 개를 셀 때는 한 번에 모으는 쪽이 눈에 띄게 빠를 수 있습니다.
그렇다고 항상 수동 루프가 답은 아닙니다. 조건이 한두 개이고 데이터가 작다면 count_if가 훨씬 읽기 쉽고, 각 결과가 서로 다른 함수에서 필요하다면 억지로 합칠 이유도 없습니다. 한 번 순회를 알고리즘으로 표현하고 싶다면 std::for_each에 여러 카운터를 캡처한 람다를 넘기거나, 결과 구조체를 누적하는 std::accumulate를 쓸 수 있습니다. 그리고 정렬된 데이터에서 특정 값의 개수를 세는 것이라면 auto [lo, hi] = std::equal_range(v.begin(), v.end(), x); auto n = hi - lo;가 O(log n)으로 끝납니다.
점수 통계 유틸리티에 조합하기
#include <algorithm>
#include <vector>
#include <numeric>
#include <cmath>
#include <iostream>
class Statistics {
public:
// 기본 통계
static void analyze(const std::vector<int>& data) {
if (data.empty()) {
std::cout << "데이터 없음" << std::endl;
return;
}
// 개수
size_t count = data.size();
// 합계
int sum = std::accumulate(data.begin(), data.end(), 0);
// 평균
double mean = static_cast<double>(sum) / count;
// 최소/최대
auto [minIt, maxIt] = std::minmax_element(data.begin(), data.end());
// 짝수/홀수
int evenCount = std::count_if(data.begin(), data.end(), [](int x) {
return x % 2 == 0;
});
int oddCount = count - evenCount;
// 양수/음수/0
int positive = std::count_if(data.begin(), data.end(), [](int x) { return x > 0; });
int negative = std::count_if(data.begin(), data.end(), [](int x) { return x < 0; });
int zero = std::count(data.begin(), data.end(), 0);
// 출력
std::cout << "=== 통계 ===" << std::endl;
std::cout << "개수: " << count << std::endl;
std::cout << "합계: " << sum << std::endl;
std::cout << "평균: " << mean << std::endl;
std::cout << "최소: " << *minIt << std::endl;
std::cout << "최대: " << *maxIt << std::endl;
std::cout << "짝수: " << evenCount << ", 홀수: " << oddCount << std::endl;
std::cout << "양수: " << positive << ", 음수: " << negative << ", 0: " << zero << std::endl;
}
// 조건 검사
static void validate(const std::vector<int>& data) {
// 모두 양수?
bool allPositive = std::all_of(data.begin(), data.end(), [](int x) {
return x > 0;
});
// 하나라도 음수?
bool hasNegative = std::any_of(data.begin(), data.end(), [](int x) {
return x < 0;
});
// 0 없음?
bool noZero = std::none_of(data.begin(), data.end(), [](int x) {
return x == 0;
});
std::cout << "\n=== 검증 ===" << std::endl;
std::cout << "모두 양수: " << std::boolalpha << allPositive << std::endl;
std::cout << "음수 있음: " << std::boolalpha << hasNegative << std::endl;
std::cout << "0 없음: " << std::boolalpha << noZero << std::endl;
}
// 범위 검사
static bool inRange(const std::vector<int>& data, int min, int max) {
return std::all_of(data.begin(), data.end(), [min, max](int x) {
return x >= min && x <= max;
});
}
};
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Statistics::analyze(data);
Statistics::validate(data);
std::cout << "\n범위 [1, 10]: " << std::boolalpha
<< Statistics::inRange(data, 1, 10) << std::endl; // true
std::cout << "범위 [1, 5]: " << std::boolalpha
<< Statistics::inRange(data, 1, 5) << std::endl; // false
return 0;
}
출력:
=== 통계 ===
개수: 10
합계: 55
평균: 5.5
최소: 1
최대: 10
짝수: 5, 홀수: 5
양수: 10, 음수: 0, 0: 0
=== 검증 ===
모두 양수: true
음수 있음: false
0 없음: true
범위 [1, 10]: true
범위 [1, 5]: false
analyze가 맨 앞에서 빈 데이터를 검사하는 것은 필수입니다. 빈 벡터라면 sum / count가 0으로 나누기(부동소수점이라 NaN)가 되고, minmax_element는 end()를 반환하므로 *minIt이 미정의 동작이 됩니다. 반면 validate와 inRange에는 빈 검사가 없어서, 빈 데이터에 대해 “모두 양수: true”, “범위 [1, 5]: true”를 출력합니다. 앞에서 설명한 공허한 참이 실제 코드에서 어떻게 드러나는지 보여 주는 예입니다.
이 유틸리티를 실제 데이터에 쓰려면 두 가지를 손봐야 합니다. 첫째, std::accumulate(data.begin(), data.end(), 0)은 초기값 0이 int라서 합계도 int로 계산됩니다. 큰 값이 많으면 오버플로가 나고, 부호 있는 정수 오버플로는 미정의 동작입니다. 초기값을 0LL로 바꾸면 long long으로 누적됩니다. 같은 이유로 double 벡터에 초기값 0을 넘기면 소수점이 매번 잘려 합계가 틀립니다(0.0을 넘겨야 함). 둘째, 짝수·양수·음수·0을 세려고 count_if와 count를 네 번 호출해 데이터를 네 번 순회합니다. 앞 절의 “한 번에 순회” 방식으로 합치면 큰 데이터에서 효과가 있습니다. oddCount = count - evenCount처럼 이미 구한 값에서 계산할 수 있는 것은 다시 세지 않은 점은 좋은 습관입니다.
시그니처와 복잡도로 본 카운트 알고리즘
함수 시그니처
#include <algorithm>
// count: 값 개수
template<class InputIt, class T>
typename iterator_traits<InputIt>::difference_type
count(InputIt first, InputIt last, const T& value);
// count_if: 조건 개수
template<class InputIt, class UnaryPredicate>
typename iterator_traits<InputIt>::difference_type
count_if(InputIt first, InputIt last, UnaryPredicate p);
// all_of: 모두 만족?
template<class InputIt, class UnaryPredicate>
bool all_of(InputIt first, InputIt last, UnaryPredicate p);
// any_of: 하나라도 만족?
template<class InputIt, class UnaryPredicate>
bool any_of(InputIt first, InputIt last, UnaryPredicate p);
// none_of: 모두 불만족?
template<class InputIt, class UnaryPredicate>
bool none_of(InputIt first, InputIt last, UnaryPredicate p);
비교 표
| 알고리즘 | 반환 타입 | 시간복잡도 | 단락 평가 | 빈 범위 |
|---|---|---|---|---|
| count | difference_type | O(n) | ❌ | 0 |
| count_if | difference_type | O(n) | ❌ | 0 |
| all_of | bool | O(n) | ✅ | true |
| any_of | bool | O(n) | ✅ | false |
| none_of | bool | O(n) | ✅ | true |
카운트 알고리즘 요약
- count: 특정 값 개수
- count_if: 조건 만족 개수
- all_of: 모두 만족? (단락 평가)
- any_of: 하나라도 만족? (단락 평가)
- none_of: 모두 불만족? (단락 평가)
- 시간복잡도: 모두 O(n)
목적별 선택표
| 목적 | 알고리즘 | 예시 |
|---|---|---|
| 특정 값 개수 | count | count(v.begin(), v.end(), 5) |
| 조건 만족 개수 | count_if | count_if(v.begin(), v.end(), isEven) |
| 모두 만족? | all_of | all_of(v.begin(), v.end(), isPositive) |
| 하나라도 만족? | any_of | any_of(v.begin(), v.end(), isNegative) |
| 모두 불만족? | none_of | none_of(v.begin(), v.end(), isZero) |
헷갈리기 쉬운 네 가지
- 빈 범위:
all_of는 true,any_of는 false - 반환 타입:
count는difference_type - 단락 평가:
any_of는 첫 true에서 중단 - 부작용 금지: predicate는 순수 함수
이어서 볼 STL 알고리즘
- C++ Algorithm Search
- C++ Algorithm Remove
- C++ 정렬 알고리즘 구현과 비교: std::sort의 pdqsort, stable_sort, 병렬 정렬, 기수 정렬
같이 보면 좋은 글
- STL 알고리즘 기본기
- C++ reverse·rotate·reverse_copy
- C++ replace·replace_if·replace_copy: 값 치환과 transform 중 무엇을 쓸까
- C++ fill·generate·iota로 범위 채우기: 테스트 데이터 생성 예제
자주 묻는 질문 (FAQ)
Q. std::count의 반환값을 int나 size_t에 바로 담아도 되나요?
A. std::count와 std::count_if는 반복자의 difference_type(보통 std::ptrdiff_t)을 반환하는 부호 있는 타입입니다. size_t처럼 부호 없는 타입이나 int에 담으면 부호·범위 변환 경고가 나오고, 아주 큰 범위에서는 값이 잘릴 수 있습니다. auto로 받거나 difference_type을 그대로 쓰고, 다른 타입과 비교해야 할 때만 명시적으로 변환하는 것이 안전합니다.