본문으로 건너뛰기
Previous
Next
C++ Algorithm Generate | '생성 알고리즘' 가이드

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

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

정리

핵심 요약

  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 Generate | ‘생성 알고리즘’ 가이드」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「C++ Algorithm Generate | ‘생성 알고리즘’ 가이드」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


자주 묻는 질문 (FAQ)

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

A. C++ fill, generate, iota로 범위를 채우고 연속 값을 만드는 법. 람다·함수 객체와 함께 쓰는 생성 알고리즘 활용을 다룹니다. 실전 예제와 코드로 개념부터 활용까지 정리합니다. C++·알고리즘·ge… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

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

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

Q. 더 깊이 공부하려면?

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


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, 알고리즘, generate, fill, STL 등으로 검색하시면 이 글이 도움이 됩니다.