C++ Algorithm Generate | "생성 알고리즘" 가이드

C++ Algorithm Generate | "생성 알고리즘" 가이드

이 글의 핵심

C++ Algorithm Generate에 대한 실전 가이드입니다.

들어가며

STL 생성 알고리즘은 컨테이너를 값으로 채우거나 함수로 생성하는 기능을 제공합니다. fill, generate, iota 등을 활용하면 초기화 코드를 간결하고 효율적으로 작성할 수 있습니다.


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
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;
}

정리

핵심 요약

  1. fill: 고정 값으로 채우기 (빠름)
  2. fill_n: N개를 고정 값으로
  3. generate: 함수로 생성 (유연함)
  4. generate_n: N개를 함수로 생성
  5. 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

관련 글

  • C++ Algorithm Copy |