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]
정리
핵심 요약
- remove: 끝으로 이동 (크기 변경 안됨)
- erase: 실제 제거 (크기 변경)
- erase-remove:
v.erase(remove(...), v.end()) - unique: 인접 중복 제거 (정렬 필요)
- 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 |