C++ Algorithm Generate | '생성 알고리즘' 가이드
이 글의 핵심
C++ fill, generate, iota로 범위를 채우고 연속 값을 만드는 법. 람다·함수 객체와 함께 쓰는 생성 알고리즘 활용을 다룹니다.
들어가며
STL 생성 알고리즘은 컨테이너를 값으로 채우거나 함수로 생성하는 기능을 제공합니다. fill, generate, iota 등을 활용하면 초기화 코드를 간결하고 효율적으로 작성할 수 있습니다.
코딩 테스트 준비하며 깨달은 것
알고리즘 문제를 풀다 보면 “이게 실무에 무슨 도움이 될까?” 하는 의문이 들 때가 있습니다. 저도 그랬습니다. 하지만 실제 프로젝트에서 성능 문제에 부딪히면, 알고리즘 지식이 얼마나 중요한지 깨닫게 됩니다. 예를 들어, 사용자 검색 기능이 느려서 고민하다가 해시 테이블을 적용하니 응답 시간이 10초에서 0.1초로 줄어든 경험이 있습니다.
코딩 테스트는 단순히 취업을 위한 관문이 아니라, 문제 해결 능력을 키우는 훈련장입니다. 처음엔 브루트 포스로 풀다가, 시간 복잡도를 개선하는 과정에서 “아, 이렇게 생각하면 되는구나” 하는 깨달음을 얻을 때의 쾌감은 말로 표현하기 어렵습니다. 이 글에서는 단순히 정답 코드만 제시하는 게 아니라, 문제를 어떻게 접근하고 최적화하는지 사고 과정을 함께 공유하겠습니다.
1. std::fill
기본 사용
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(10);
// 모두 42로 채우기
std::fill(v.begin(), v.end(), 42);
for (int x : v) {
std::cout << x << " "; // 42 42 42 42 42 42 42 42 42 42
}
std::cout << std::endl;
return 0;
}
std::fill_n
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(10, 0); // 모두 0으로 초기화
// 처음 5개만 1로
std::fill_n(v.begin(), 5, 1);
for (int x : v) {
std::cout << x << " "; // 1 1 1 1 1 0 0 0 0 0
}
std::cout << std::endl;
return 0;
}
함수 시그니처:
template<class ForwardIt, class T>
void fill(ForwardIt first, ForwardIt last, const T& value);
template<class OutputIt, class Size, class T>
OutputIt fill_n(OutputIt first, Size count, const T& value);
2. std::generate
기본 사용
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(10);
int counter = 0;
// 함수로 생성
std::generate(v.begin(), v.end(), [&counter]() {
return counter++;
});
for (int x : v) {
std::cout << x << " "; // 0 1 2 3 4 5 6 7 8 9
}
std::cout << std::endl;
return 0;
}
std::generate_n
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v;
// back_inserter로 자동 확장
std::generate_n(std::back_inserter(v), 5, []() {
return rand() % 100;
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
함수 시그니처:
template<class ForwardIt, class Generator>
void generate(ForwardIt first, ForwardIt last, Generator g);
template<class OutputIt, class Size, class Generator>
OutputIt generate_n(OutputIt first, Size count, Generator g);
3. std::iota
기본 사용
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(10);
// 1부터 순차 생성
std::iota(v.begin(), v.end(), 1);
for (int x : v) {
std::cout << x << " "; // 1 2 3 4 5 6 7 8 9 10
}
std::cout << std::endl;
return 0;
}
다양한 타입
#include <numeric>
#include <vector>
#include <iostream>
int main() {
// double로 순차 생성
std::vector<double> v(5);
std::iota(v.begin(), v.end(), 1.5);
// 1.5, 2.5, 3.5, 4.5, 5.5
// char로 순차 생성
std::vector<char> chars(5);
std::iota(chars.begin(), chars.end(), 'A');
// 'A', 'B', 'C', 'D', 'E'
for (double d : v) {
std::cout << d << " ";
}
std::cout << std::endl;
for (char c : chars) {
std::cout << c << " ";
}
std::cout << std::endl;
return 0;
}
4. 실전 예제
예제 1: 난수 생성
#include <algorithm>
#include <vector>
#include <random>
#include <iostream>
int main() {
std::vector<int> v(10);
// 난수 생성기 설정
std::mt19937 gen{std::random_device{}()};
std::uniform_int_distribution<> dist{1, 100};
// 난수로 채우기
std::generate(v.begin(), v.end(), [&]() {
return dist(gen);
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
예제 2: 함수 객체 (Functor)
#include <algorithm>
#include <vector>
#include <iostream>
class Counter {
int count;
int step;
public:
Counter(int start = 0, int step = 1)
: count(start), step(step) {}
int operator()() {
int result = count;
count += step;
return result;
}
};
int main() {
std::vector<int> v(10);
// 0, 2, 4, 6, ...
std::generate(v.begin(), v.end(), Counter{0, 2});
for (int x : v) {
std::cout << x << " "; // 0 2 4 6 8 10 12 14 16 18
}
std::cout << std::endl;
return 0;
}
예제 3: 구조체 초기화
#include <algorithm>
#include <vector>
#include <iostream>
struct Point {
int x, y;
};
int main() {
std::vector<Point> points(5);
int id = 0;
std::generate(points.begin(), points.end(), [&id]() {
return Point{id, id * 10};
id++;
});
for (const auto& p : points) {
std::cout << "(" << p.x << ", " << p.y << ") ";
}
std::cout << std::endl;
return 0;
}
예제 4: ID 생성기
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
class IDGenerator {
std::string prefix;
int counter;
public:
IDGenerator(const std::string& prefix)
: prefix(prefix), counter(1) {}
std::string operator()() {
return prefix + std::to_string(counter++);
}
};
int main() {
std::vector<std::string> ids(5);
std::generate(ids.begin(), ids.end(), IDGenerator{"USER_"});
for (const auto& id : ids) {
std::cout << id << std::endl;
}
// USER_1
// USER_2
// USER_3
// USER_4
// USER_5
return 0;
}
5. 생성 알고리즘 비교
| 알고리즘 | 헤더 | 용도 | 시간 복잡도 |
|---|---|---|---|
fill | <algorithm> | 고정 값으로 채우기 | O(N) |
fill_n | <algorithm> | N개를 고정 값으로 | O(N) |
generate | <algorithm> | 함수로 생성 | O(N) |
generate_n | <algorithm> | N개를 함수로 생성 | O(N) |
iota | <numeric> | 순차 증가 값 생성 | O(N) |
함수 시그니처:
fill 함수의 구현 예제입니다.
// fill
template<class ForwardIt, class T>
void fill(ForwardIt first, ForwardIt last, const T& value);
// generate
template<class ForwardIt, class Generator>
void generate(ForwardIt first, ForwardIt last, Generator g);
// iota
template<class ForwardIt, class T>
void iota(ForwardIt first, ForwardIt last, T value);
6. 자주 발생하는 문제
문제 1: 크기 미지정
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v; // 크기 0
// ❌ 크기가 0이면 아무 일도 안 함
std::fill(v.begin(), v.end(), 42);
std::cout << v.size() << std::endl; // 0
// ✅ 크기 지정
v.resize(10);
std::fill(v.begin(), v.end(), 42);
std::cout << v.size() << std::endl; // 10
// ✅ 또는 생성자에서 크기 지정
std::vector<int> v2(10);
std::fill(v2.begin(), v2.end(), 42);
return 0;
}
해결책: 컨테이너 크기를 미리 지정하거나 back_inserter를 사용하세요.
문제 2: 캡처 방식
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
int counter = 0;
std::vector<int> v(10);
// ❌ 값 캡처 (복사본 수정)
std::generate(v.begin(), v.end(), [=]() mutable {
return counter++; // 복사본만 증가
});
std::cout << "counter: " << counter << std::endl; // 0 (변경 안 됨)
// ✅ 참조 캡처
counter = 0;
std::generate(v.begin(), v.end(), [&counter]() {
return counter++; // 원본 증가
});
std::cout << "counter: " << counter << std::endl; // 10
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
해결책: 상태를 유지하려면 참조 캡처([&])를 사용하세요.
문제 3: 난수 생성
#include <algorithm>
#include <vector>
#include <random>
#include <iostream>
int main() {
std::vector<int> v(10);
// ❌ rand() (C 스타일, 품질 낮음)
std::generate(v.begin(), v.end(), []() {
return rand() % 100;
});
// ✅ C++11 random (품질 높음)
std::mt19937 gen{std::random_device{}()};
std::uniform_int_distribution<> dist{0, 99};
std::generate(v.begin(), v.end(), [&]() {
return dist(gen);
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
해결책: C++11 <random> 라이브러리를 사용하세요.
문제 4: 성능
#include <algorithm>
#include <vector>
#include <chrono>
#include <iostream>
int main() {
std::vector<int> v(1000000);
// fill: 최적화됨 (빠름)
auto start1 = std::chrono::high_resolution_clock::now();
std::fill(v.begin(), v.end(), 0);
auto end1 = std::chrono::high_resolution_clock::now();
// generate: 함수 호출 오버헤드 (느림)
auto start2 = std::chrono::high_resolution_clock::now();
std::generate(v.begin(), v.end(), []() { return 0; });
auto end2 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1).count();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2).count();
std::cout << "fill: " << duration1 << " μs" << std::endl;
std::cout << "generate: " << duration2 << " μs" << std::endl;
return 0;
}
해결책: 고정 값은 fill을, 동적 생성은 generate를 사용하세요.
7. 실전 예제: 테스트 데이터 생성기
#include <algorithm>
#include <numeric>
#include <vector>
#include <random>
#include <iostream>
#include <string>
class TestDataGenerator {
std::mt19937 gen;
public:
TestDataGenerator() : gen(std::random_device{}()) {}
// 난수 배열 생성
std::vector<int> randomInts(size_t count, int min, int max) {
std::vector<int> result(count);
std::uniform_int_distribution<> dist{min, max};
std::generate(result.begin(), result.end(), [&]() {
return dist(gen);
});
return result;
}
// 순차 배열 생성
std::vector<int> sequence(size_t count, int start = 0) {
std::vector<int> result(count);
std::iota(result.begin(), result.end(), start);
return result;
}
// 고정 값 배열 생성
std::vector<int> constant(size_t count, int value) {
std::vector<int> result(count);
std::fill(result.begin(), result.end(), value);
return result;
}
// 패턴 배열 생성
std::vector<int> pattern(size_t count, const std::vector<int>& pattern) {
std::vector<int> result(count);
size_t patternSize = pattern.size();
std::generate(result.begin(), result.end(), [&, i = 0]() mutable {
return pattern[i++ % patternSize];
});
return result;
}
// 사용자 정의 생성
template<typename Generator>
std::vector<int> custom(size_t count, Generator gen) {
std::vector<int> result(count);
std::generate(result.begin(), result.end(), gen);
return result;
}
};
int main() {
TestDataGenerator tdg;
// 난수 10개 (1~100)
auto random = tdg.randomInts(10, 1, 100);
std::cout << "난수: ";
for (int x : random) std::cout << x << " ";
std::cout << std::endl;
// 순차 10개 (0부터)
auto seq = tdg.sequence(10);
std::cout << "순차: ";
for (int x : seq) std::cout << x << " ";
std::cout << std::endl;
// 고정 값 10개
auto constant = tdg.constant(10, 42);
std::cout << "고정: ";
for (int x : constant) std::cout << x << " ";
std::cout << std::endl;
// 패턴 반복 (1, 2, 3, 1, 2, 3, ...)
auto patt = tdg.pattern(10, {1, 2, 3});
std::cout << "패턴: ";
for (int x : patt) std::cout << x << " ";
std::cout << std::endl;
// 사용자 정의 (제곱수)
auto custom = tdg.custom(5, [i = 0]() mutable {
return i * i++;
});
std::cout << "제곱: ";
for (int x : custom) std::cout << x << " ";
std::cout << std::endl;
return 0;
}
정리
핵심 요약
- fill: 고정 값으로 채우기 (빠름)
- fill_n: N개를 고정 값으로
- generate: 함수로 생성 (유연함)
- generate_n: N개를 함수로 생성
- iota: 순차 증가 값 생성 (
<numeric>)
생성 알고리즘 선택 가이드
| 상황 | 권장 알고리즘 | 이유 |
|---|---|---|
| 고정 값 | fill | 최적화, 빠름 |
| 순차 값 | iota | 간결함 |
| 난수 | generate + <random> | 품질 높음 |
| 복잡한 로직 | generate + 람다 | 유연함 |
| 상태 유지 | generate + 참조 캡처 | 상태 공유 |
실전 팁
성능:
- 고정 값은
fill사용 (최적화됨) generate는 함수 호출 오버헤드 있음- 대량 데이터는 병렬 알고리즘 고려 (
std::execution::par)
가독성:
- 간단한 순차 값은
iota - 복잡한 로직은 함수 객체로 분리
- 람다는 간결하게 유지
주의사항:
- 컨테이너 크기 미리 지정
- 상태 유지는 참조 캡처
- 난수는 C++11
<random>사용
다음 단계
- C++ Algorithm Reverse
- C++ Algorithm Count
- C++ Algorithm Transform
관련 글
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. C++ fill, generate, iota로 범위를 채우고 연속 값을 만드는 법. 람다·함수 객체와 함께 쓰는 생성 알고리즘 활용을 다룹니다. 실전 예제와 코드로 개념부터 활용까지 정리합니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Algorithm Reverse | reverse·rotate·reverse_copy 완벽 정리
- C++ Algorithm Count | ‘카운트 알고리즘’ 가이드
- C++ Algorithm Remove | ‘제거 알고리즘’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, 알고리즘, generate, fill, STL 등으로 검색하시면 이 글이 도움이 됩니다.