C++ Algorithm Remove | "제거 알고리즘" 가이드

C++ Algorithm Remove | "제거 알고리즘" 가이드

이 글의 핵심

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

들어가며

STL 제거 알고리즘은 요소를 제거하는 것이 아니라 끝으로 이동합니다. 실제 제거는 erase와 함께 사용하는 erase-remove idiom이 표준 패턴입니다.


1. remove 알고리즘

기본 동작

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

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // remove: 2를 끝으로 이동
    auto newEnd = std::remove(v.begin(), v.end(), 2);
    
    std::cout << "remove 후: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    std::cout << "크기: " << v.size() << std::endl;  // 7 (변경 안됨!)
    
    // erase: 실제 제거
    v.erase(newEnd, v.end());
    
    std::cout << "erase 후: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    std::cout << "크기: " << v.size() << std::endl;  // 4
    
    return 0;
}

출력:

원본: 1 2 3 2 4 2 5
remove 후: 1 3 4 5 4 2 5  (끝 부분은 쓰레기 값)
크기: 7
erase 후: 1 3 4 5
크기: 4

erase-remove idiom

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

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    // ✅ erase-remove idiom (한 줄)
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    
    for (int x : v) std::cout << x << " ";  // 1 3 4 5
    std::cout << std::endl;
    
    return 0;
}

2. remove_if

조건부 제거

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

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 짝수 제거
    v.erase(
        std::remove_if(v.begin(), v.end(),  {
            return x % 2 == 0;
        }),
        v.end()
    );
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 9
    std::cout << std::endl;
    
    return 0;
}

복잡한 조건

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

struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people = {
        {"Alice", 25},
        {"Bob", 17},
        {"Charlie", 30},
        {"David", 15}
    };
    
    // 미성년자 제거
    people.erase(
        std::remove_if(people.begin(), people.end(),  {
            return p.age < 18;
        }),
        people.end()
    );
    
    for (const auto& p : people) {
        std::cout << p.name << " (" << p.age << ")" << std::endl;
    }
    
    return 0;
}

출력:

Alice (25)
Charlie (30)

3. unique

중복 제거

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

int main() {
    std::vector<int> v = {1, 1, 2, 2, 2, 3, 3, 4, 5, 5};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // ❌ 정렬 안 하면 인접 중복만 제거
    auto v1 = v;
    v1.erase(std::unique(v1.begin(), v1.end()), v1.end());
    
    std::cout << "정렬 안 함: ";
    for (int x : v1) std::cout << x << " ";  // 1 2 3 4 5
    std::cout << std::endl;
    
    // ✅ 정렬 후 중복 제거
    std::vector<int> v2 = {1, 2, 1, 3, 2, 4, 3, 5};
    std::sort(v2.begin(), v2.end());
    v2.erase(std::unique(v2.begin(), v2.end()), v2.end());
    
    std::cout << "정렬 후: ";
    for (int x : v2) std::cout << x << " ";  // 1 2 3 4 5
    std::cout << std::endl;
    
    return 0;
}

커스텀 비교

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>

int main() {
    std::vector<std::string> words = {"apple", "APPLE", "banana", "Banana"};
    
    // 대소문자 무시 중복 제거
    std::sort(words.begin(), words.end(),  {
        return std::lexicographical_compare(
            a.begin(), a.end(),
            b.begin(), b.end(),
             { return std::tolower(c1) < std::tolower(c2); }
        );
    });
    
    words.erase(
        std::unique(words.begin(), words.end(),  {
            return std::equal(
                a.begin(), a.end(),
                b.begin(), b.end(),
                 { return std::tolower(c1) == std::tolower(c2); }
            );
        }),
        words.end()
    );
    
    for (const auto& word : words) {
        std::cout << word << std::endl;
    }
    
    return 0;
}

출력:

apple
banana

4. 실전 예제

예제 1: 문자열 정리

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

std::string cleanString(std::string str) {
    // 공백 제거
    str.erase(
        std::remove_if(str.begin(), str.end(),  {
            return std::isspace(c);
        }),
        str.end()
    );
    
    return str;
}

std::string removeNonAlpha(std::string str) {
    // 알파벳 아닌 문자 제거
    str.erase(
        std::remove_if(str.begin(), str.end(),  {
            return !std::isalpha(c);
        }),
        str.end()
    );
    
    return str;
}

int main() {
    std::string text1 = "  Hello   World  ";
    std::cout << "[" << cleanString(text1) << "]" << std::endl;
    // [HelloWorld]
    
    std::string text2 = "Hello123World456";
    std::cout << "[" << removeNonAlpha(text2) << "]" << std::endl;
    // [HelloWorld]
    
    return 0;
}

예제 2: 데이터 필터링

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

struct Product {
    std::string name;
    double price;
    bool inStock;
};

int main() {
    std::vector<Product> products = {
        {"Laptop", 1200.0, true},
        {"Mouse", 25.0, false},
        {"Keyboard", 75.0, true},
        {"Monitor", 300.0, false},
        {"Headset", 80.0, true}
    };
    
    // 재고 없는 제품 제거
    products.erase(
        std::remove_if(products.begin(), products.end(),  {
            return !p.inStock;
        }),
        products.end()
    );
    
    std::cout << "재고 있는 제품:" << std::endl;
    for (const auto& p : products) {
        std::cout << "- " << p.name << ": $" << p.price << std::endl;
    }
    
    return 0;
}

출력:

재고 있는 제품:
- Laptop: $1200
- Keyboard: $75
- Headset: $80

예제 3: 중복 제거

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

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    
    std::cout << "원본: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    // 정렬 후 중복 제거
    std::sort(v.begin(), v.end());
    v.erase(std::unique(v.begin(), v.end()), v.end());
    
    std::cout << "중복 제거: ";
    for (int x : v) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

출력:

원본: 3 1 4 1 5 9 2 6 5 3
중복 제거: 1 2 3 4 5 6 9

5. 자주 발생하는 문제

문제 1: erase 누락

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

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4};
    
    // ❌ erase 없음 (크기 변경 안됨)
    auto newEnd = std::remove(v.begin(), v.end(), 2);
    
    std::cout << "크기: " << v.size() << std::endl;  // 5 (변경 안됨!)
    std::cout << "내용: ";
    for (int x : v) std::cout << x << " ";  // 1 3 4 2 4 (쓰레기 값)
    std::cout << std::endl;
    
    // ✅ erase 호출
    v = {1, 2, 3, 2, 4};
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    
    std::cout << "크기: " << v.size() << std::endl;  // 3
    std::cout << "내용: ";
    for (int x : v) std::cout << x << " ";  // 1 3 4
    std::cout << std::endl;
    
    return 0;
}

문제 2: unique 정렬

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

int main() {
    std::vector<int> v = {1, 2, 1, 3, 2};
    
    // ❌ 정렬 안 됨 (인접 중복만 제거)
    auto v1 = v;
    v1.erase(std::unique(v1.begin(), v1.end()), v1.end());
    
    std::cout << "정렬 안 함: ";
    for (int x : v1) std::cout << x << " ";  // 1 2 1 3 2 (중복 남음)
    std::cout << std::endl;
    
    // ✅ 정렬 후
    auto v2 = v;
    std::sort(v2.begin(), v2.end());
    v2.erase(std::unique(v2.begin(), v2.end()), v2.end());
    
    std::cout << "정렬 후: ";
    for (int x : v2) std::cout << x << " ";  // 1 2 3
    std::cout << std::endl;
    
    return 0;
}

문제 3: 성능

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

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // ❌ 여러 번 호출 (비효율)
    v.erase(std::remove(v.begin(), v.end(), 2), v.end());
    v.erase(std::remove(v.begin(), v.end(), 4), v.end());
    v.erase(std::remove(v.begin(), v.end(), 6), v.end());
    
    // ✅ 한 번에 (효율적)
    v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    v.erase(
        std::remove_if(v.begin(), v.end(),  {
            return x == 2 || x == 4 || x == 6;
        }),
        v.end()
    );
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 8 9 10
    std::cout << std::endl;
    
    return 0;
}

문제 4: list vs vector

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

int main() {
    // vector: erase-remove idiom
    std::vector<int> vec = {1, 2, 3, 2, 4};
    vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
    
    std::cout << "vector: ";
    for (int x : vec) std::cout << x << " ";
    std::cout << std::endl;
    
    // list: remove 멤버 함수 (더 효율적)
    std::list<int> lst = {1, 2, 3, 2, 4};
    lst.remove(2);  // O(n), 재할당 없음
    
    std::cout << "list: ";
    for (int x : lst) std::cout << x << " ";
    std::cout << std::endl;
    
    return 0;
}

6. C++20 std::erase

간결한 제거

#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
    
    // C++20: std::erase (간결)
    std::erase(v, 2);
    
    for (int x : v) std::cout << x << " ";  // 1 3 4 5
    std::cout << std::endl;
    
    return 0;
}

std::erase_if

#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // C++20: std::erase_if (간결)
    std::erase_if(v,  { return x % 2 == 0; });
    
    for (int x : v) std::cout << x << " ";  // 1 3 5 7 9
    std::cout << std::endl;
    
    return 0;
}

7. 실전 예제: 데이터 정리 유틸리티

#include <algorithm>
#include <vector>
#include <string>
#include <cctype>
#include <iostream>

class DataCleaner {
public:
    // 빈 문자열 제거
    static void removeEmpty(std::vector<std::string>& vec) {
        vec.erase(
            std::remove_if(vec.begin(), vec.end(),  {
                return s.empty();
            }),
            vec.end()
        );
    }
    
    // 중복 제거
    static void removeDuplicates(std::vector<int>& vec) {
        std::sort(vec.begin(), vec.end());
        vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
    }
    
    // 범위 밖 값 제거
    static void removeOutOfRange(std::vector<int>& vec, int min, int max) {
        vec.erase(
            std::remove_if(vec.begin(), vec.end(), [min, max](int x) {
                return x < min || x > max;
            }),
            vec.end()
        );
    }
    
    // 공백 문자 제거
    static std::string removeWhitespace(std::string str) {
        str.erase(
            std::remove_if(str.begin(), str.end(),  {
                return std::isspace(c);
            }),
            str.end()
        );
        return str;
    }
    
    // 연속 공백을 하나로
    static std::string normalizeSpaces(std::string str) {
        // 앞뒤 공백 제거
        auto start = std::find_if_not(str.begin(), str.end(),  {
            return std::isspace(c);
        });
        auto end = std::find_if_not(str.rbegin(), str.rend(),  {
            return std::isspace(c);
        }).base();
        
        str = std::string(start, end);
        
        // 연속 공백을 하나로
        str.erase(
            std::unique(str.begin(), str.end(),  {
                return std::isspace(a) && std::isspace(b);
            }),
            str.end()
        );
        
        return str;
    }
};

int main() {
    // 빈 문자열 제거
    std::vector<std::string> strings = {"hello", "", "world", "", "test"};
    DataCleaner::removeEmpty(strings);
    std::cout << "빈 문자열 제거: ";
    for (const auto& s : strings) std::cout << s << " ";
    std::cout << std::endl;
    
    // 중복 제거
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
    DataCleaner::removeDuplicates(numbers);
    std::cout << "중복 제거: ";
    for (int x : numbers) std::cout << x << " ";
    std::cout << std::endl;
    
    // 범위 밖 제거
    std::vector<int> values = {1, 5, 10, 15, 20, 25, 30};
    DataCleaner::removeOutOfRange(values, 10, 20);
    std::cout << "범위 밖 제거: ";
    for (int x : values) std::cout << x << " ";
    std::cout << std::endl;
    
    // 공백 제거
    std::string text = "  Hello   World  ";
    std::cout << "공백 제거: [" << DataCleaner::removeWhitespace(text) << "]" << std::endl;
    
    // 공백 정규화
    std::cout << "공백 정규화: [" << DataCleaner::normalizeSpaces(text) << "]" << std::endl;
    
    return 0;
}

출력:

빈 문자열 제거: hello world test
중복 제거: 1 2 3 4 5 6 9
범위 밖 제거: 10 15 20
공백 제거: [HelloWorld]
공백 정규화: [Hello World]

정리

핵심 요약

  1. remove: 끝으로 이동 (크기 변경 안됨)
  2. erase: 실제 제거 (크기 변경)
  3. erase-remove: v.erase(remove(...), v.end())
  4. unique: 인접 중복 제거 (정렬 필요)
  5. C++20: std::erase, std::erase_if

제거 알고리즘 비교

알고리즘동작시간복잡도사용 시기
remove값 제거O(n)특정 값
remove_if조건 제거O(n)조건부
unique중복 제거O(n)정렬 후
erase실제 제거O(n)remove 후
list::remove직접 제거O(n)list 전용

실전 팁

사용 원칙:

  • remove는 항상 erase와 함께
  • unique는 정렬 후 사용
  • list는 멤버 함수 remove 사용
  • C++20은 std::erase 사용

성능:

  • 여러 조건은 remove_if로 한 번에
  • list는 멤버 함수가 더 효율적
  • vector는 erase-remove idiom
  • 대량 제거는 새 컨테이너 생성 고려

주의사항:

  • remove만 호출하면 쓰레기 값 남음
  • unique는 인접 중복만 제거
  • 반복자 무효화 주의
  • 빈 범위 체크

다음 단계

  • C++ Algorithm Replace
  • C++ Algorithm Copy
  • C++ Algorithm Sort

관련 글

  • C++ Algorithm Copy |
  • C++ Algorithm Count |
  • C++ Algorithm Generate |
  • C++ 알고리즘 |
  • C++ Algorithm Heap |