C++ Algorithm Replace | "치환 알고리즘" 가이드

C++ Algorithm Replace | "치환 알고리즘" 가이드

이 글의 핵심

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

들어가며

C++ STL의 replace 알고리즘은 컨테이너의 요소를 효율적으로 치환할 수 있게 해줍니다. 값 기반 치환과 조건 기반 치환을 모두 지원합니다.


1. std::replace

기본 사용

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    std::cout << "원본: ";
    for (int x : v) {
        std::cout << x << " ";  // 1 2 3 2 4 2 5
    }
    std::cout << std::endl;
    
    // 2를 9로 치환
    std::replace(v.begin(), v.end(), 2, 9);
    
    std::cout << "치환 후: ";
    for (int x : v) {
        std::cout << x << " ";  // 1 9 3 9 4 9 5
    }
    std::cout << std::endl;
}

문자열 치환

#include <iostream>
#include <algorithm>
#include <string>

int main() {
    std::string text = "Hello World";
    
    // 공백을 밑줄로 치환
    std::replace(text.begin(), text.end(), ' ', '_');
    std::cout << text << std::endl;  // Hello_World
    
    // 특정 문자 치환
    std::string code = "int x = 10;";
    std::replace(code.begin(), code.end(), ' ', '\t');
    std::cout << code << std::endl;  // int	x	=	10;
}

함수 시그니처:

template<class ForwardIt, class T>
void replace(ForwardIt first, ForwardIt last,
             const T& old_value, const T& new_value);

핵심 개념:

  • 원본 수정: 컨테이너를 직접 수정
  • 모든 일치: 일치하는 모든 요소 치환
  • 시간 복잡도: O(n)

2. std::replace_if

조건 기반 치환

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 짝수를 0으로 치환
    std::replace_if(v.begin(), v.end(),
         { return x % 2 == 0; }, 0);
    
    for (int x : v) {
        std::cout << x << " ";  // 1 0 3 0 5 0 7 0 9 0
    }
    std::cout << std::endl;
}

복잡한 조건

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> scores = {45, 67, 89, 34, 92, 78, 56};
    
    // 60점 미만을 0으로
    std::replace_if(scores.begin(), scores.end(),
         { return score < 60; }, 0);
    
    std::cout << "점수: ";
    for (int score : scores) {
        std::cout << score << " ";  // 0 67 89 0 92 78 0
    }
    std::cout << std::endl;
}

3. std::replace_copy

원본 유지하며 치환

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> src = {1, 2, 3, 2, 4, 2, 5};
    std::vector<int> dst;
    
    // 복사하며 치환 (원본 유지)
    std::replace_copy(src.begin(), src.end(), 
                      std::back_inserter(dst), 2, 9);
    
    std::cout << "원본: ";
    for (int x : src) {
        std::cout << x << " ";  // 1 2 3 2 4 2 5 (변경 없음)
    }
    std::cout << std::endl;
    
    std::cout << "복사본: ";
    for (int x : dst) {
        std::cout << x << " ";  // 1 9 3 9 4 9 5
    }
    std::cout << std::endl;
}

std::replace_copy_if

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> dst;
    
    // 짝수를 0으로 치환하며 복사
    std::replace_copy_if(src.begin(), src.end(),
                         std::back_inserter(dst),
                          { return x % 2 == 0; }, 0);
    
    std::cout << "원본: ";
    for (int x : src) {
        std::cout << x << " ";  // 1 2 3 4 5 6 7 8 9 10
    }
    std::cout << std::endl;
    
    std::cout << "복사본: ";
    for (int x : dst) {
        std::cout << x << " ";  // 1 0 3 0 5 0 7 0 9 0
    }
    std::cout << std::endl;
}

4. 실전 예제

예제 1: 데이터 정제

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    // 센서 데이터 (오류 값 -1 포함)
    std::vector<int> sensorData = {23, -1, 25, 24, -1, 26, 23, -1};
    
    // 오류 값을 평균값으로 치환
    int validSum = 0;
    int validCount = 0;
    
    for (int value : sensorData) {
        if (value != -1) {
            validSum += value;
            validCount++;
        }
    }
    
    int average = validSum / validCount;
    
    std::replace(sensorData.begin(), sensorData.end(), -1, average);
    
    std::cout << "정제된 데이터: ";
    for (int value : sensorData) {
        std::cout << value << " ";  // 23 24 25 24 24 26 23 24
    }
    std::cout << std::endl;
}

예제 2: 텍스트 처리

#include <iostream>
#include <algorithm>
#include <string>

int main() {
    std::string text = "Hello, World! How are you?";
    
    // 구두점을 공백으로
    std::replace_if(text.begin(), text.end(),
         { return c == ',' || c == '!' || c == '?'; }, ' ');
    
    std::cout << text << std::endl;
    // Hello  World  How are you 
    
    // 연속된 공백 제거는 unique 사용
}

예제 3: 범위 치환

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> grades = {95, 45, 78, 34, 89, 67, 23, 91};
    
    // 60점 미만을 60점으로 상향 (최소 점수 보장)
    std::replace_if(grades.begin(), grades.end(),
         { return grade < 60; }, 60);
    
    std::cout << "조정된 점수: ";
    for (int grade : grades) {
        std::cout << grade << " ";  // 95 60 78 60 89 67 60 91
    }
    std::cout << std::endl;
}

5. 치환 알고리즘 정리

알고리즘 비교

알고리즘원본 수정조건시간 복잡도
replace값 비교O(n)
replace_ifPredicateO(n)
replace_copy값 비교O(n)
replace_copy_ifPredicateO(n)

함수 시그니처

// 값 치환 (원본 수정)
template<class ForwardIt, class T>
void replace(ForwardIt first, ForwardIt last,
             const T& old_value, const T& new_value);

// 조건 치환 (원본 수정)
template<class ForwardIt, class UnaryPredicate, class T>
void replace_if(ForwardIt first, ForwardIt last,
                UnaryPredicate pred, const T& new_value);

// 복사하며 값 치환
template<class InputIt, class OutputIt, class T>
OutputIt replace_copy(InputIt first, InputIt last, OutputIt d_first,
                      const T& old_value, const T& new_value);

// 복사하며 조건 치환
template<class InputIt, class OutputIt, class UnaryPredicate, class T>
OutputIt replace_copy_if(InputIt first, InputIt last, OutputIt d_first,
                         UnaryPredicate pred, const T& new_value);

6. 자주 발생하는 문제

문제 1: 원본 수정 vs 복사

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4};
    
    // replace: 원본 수정
    std::replace(v.begin(), v.end(), 2, 9);
    std::cout << "원본 수정: ";
    for (int x : v) {
        std::cout << x << " ";  // 1 9 3 9 4
    }
    std::cout << std::endl;
    
    // ✅ 원본 유지하려면 replace_copy
    std::vector<int> v2 = {1, 2, 3, 2, 4};
    std::vector<int> dst;
    std::replace_copy(v2.begin(), v2.end(), 
                      std::back_inserter(dst), 2, 9);
    
    std::cout << "원본: ";
    for (int x : v2) {
        std::cout << x << " ";  // 1 2 3 2 4 (변경 없음)
    }
    std::cout << std::endl;
}

문제 2: 여러 값 치환

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // ❌ 비효율적: 여러 번 순회
    std::replace(v.begin(), v.end(), 2, 0);
    std::replace(v.begin(), v.end(), 4, 0);
    
    // ✅ 효율적: 한 번 순회
    std::vector<int> v2 = {1, 2, 3, 4, 5};
    std::replace_if(v2.begin(), v2.end(),
         { return x == 2 || x == 4; }, 0);
    
    for (int x : v2) {
        std::cout << x << " ";  // 1 0 3 0 5
    }
    std::cout << std::endl;
}

문제 3: std::string::replace와 혼동

#include <iostream>
#include <algorithm>
#include <string>

int main() {
    std::string text = "hello world";
    
    // std::replace (알고리즘): 문자 하나씩 치환
    std::replace(text.begin(), text.end(), 'l', 'L');
    std::cout << text << std::endl;  // heLLo worLd
    
    // std::string::replace (멤버 함수): 부분 문자열 치환
    text.replace(0, 5, "HELLO");
    std::cout << text << std::endl;  // HELLO worLd
    
    // 다른 기능!
}

문제 4: 반복자 무효화

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // ✅ replace는 반복자를 무효화하지 않음
    auto it = v.begin();
    std::replace(v.begin(), v.end(), 3, 99);
    
    std::cout << *it << std::endl;  // 1 (여전히 유효)
    
    // 주의: 크기 변경 연산(insert, erase)은 반복자 무효화
}

7. transform vs replace

차이점

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<int> v2 = {1, 2, 3, 4, 5};
    
    // replace: 특정 값만 치환
    std::replace(v1.begin(), v1.end(), 2, 9);
    std::cout << "replace: ";
    for (int x : v1) {
        std::cout << x << " ";  // 1 9 3 4 5
    }
    std::cout << std::endl;
    
    // transform: 모든 요소 변환
    std::transform(v2.begin(), v2.end(), v2.begin(),
         { return x * 2; });
    std::cout << "transform: ";
    for (int x : v2) {
        std::cout << x << " ";  // 2 4 6 8 10
    }
    std::cout << std::endl;
}

언제 무엇을 사용할까

상황사용할 알고리즘
특정 값을 다른 값으로replace
조건에 맞는 값만 치환replace_if
모든 요소를 변환transform
원본 유지하며 치환replace_copy

8. 실전 예제: 데이터 전처리

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>

class DataPreprocessor {
public:
    // 이상치 제거 (평균으로 치환)
    static void replaceOutliers(std::vector<double>& data, double threshold) {
        double mean = std::accumulate(data.begin(), data.end(), 0.0) / data.size();
        
        std::replace_if(data.begin(), data.end(),
            [mean, threshold](double x) {
                return std::abs(x - mean) > threshold;
            }, mean);
    }
    
    // 음수를 0으로
    static void replaceNegatives(std::vector<int>& data) {
        std::replace_if(data.begin(), data.end(),
             { return x < 0; }, 0);
    }
    
    // 결측치 처리
    static void replaceMissing(std::vector<int>& data, int missingValue, int replacement) {
        std::replace(data.begin(), data.end(), missingValue, replacement);
    }
};

int main() {
    // 센서 데이터
    std::vector<double> temperatures = {23.5, 24.0, 100.0, 23.8, 24.2, -50.0, 24.5};
    
    std::cout << "원본: ";
    for (double t : temperatures) {
        std::cout << t << " ";
    }
    std::cout << std::endl;
    
    // 이상치 제거 (평균에서 50 이상 차이)
    DataPreprocessor::replaceOutliers(temperatures, 50.0);
    
    std::cout << "전처리 후: ";
    for (double t : temperatures) {
        std::cout << t << " ";
    }
    std::cout << std::endl;
}

정리

핵심 요약

  1. replace: 특정 값을 다른 값으로 치환 (원본 수정)
  2. replace_if: 조건에 맞는 값 치환
  3. replace_copy: 복사하며 치환 (원본 유지)
  4. replace_copy_if: 복사하며 조건 치환
  5. 시간 복잡도: 모두 O(n)

실전 팁

  1. 선택 기준

    • 원본 수정 가능: replace, replace_if
    • 원본 유지: replace_copy, replace_copy_if
    • 특정 값: replace, replace_copy
    • 조건 기반: replace_if, replace_copy_if
  2. 성능 최적화

    • 여러 값 치환 시 replace_if 사용
    • 단일 패스로 처리
    • 불필요한 복사 피하기
  3. 주의사항

    • std::string::replace와 다름
    • 반복자는 유효 (크기 변경 없음)
    • Predicate는 부작용 없어야 함

다음 단계

  • C++ Algorithm Reverse
  • C++ Algorithm Remove
  • C++ Algorithm Transform

관련 글

  • C++ STL 알고리즘 기초 완벽 가이드 | sort·find