はじめに
C++で、vector
などのコンテナから、重複している要素を全て取り除き、ユニークな(一意な)要素だけの状態にしたい、という場面は頻繁にあります。
この目的のために、標準ライブラリ <algorithm>
には std::unique
という関数が用意されています。しかし、unique
は少し特殊な動作をし、要素を本当に削除するわけではありません。unique
は、重複している要素をコンテナの後方に移動させ、「ここからが重複部分の始まりです」という位置を示すイテレータを返すだけです。
実際にコンテナからこれらの不要な要素を完全に削除するには、unique
が返したイテレータをコンテナの .erase()
メソッドに渡す必要があります。
この記事では、この sort
→ unique
→ .erase
という一連の流れ(Erase-Removeイディオム)を解説します。
【重要】事前条件: unique
の前には sort
が必要
std::unique
は、連続して重複している要素しか検出しません。{1, 5, 1, 5}
のような、重複が離れているコンテナに unique
をそのまま適用しても、何も起こりません。
そのため、全ての重複要素を正しく処理するには、unique
を呼び出す前に、必ずコンテナを std::sort
でソートし、同じ値が隣り合うようにしておく必要があります。
重複要素を削除するサンプルコード
このコードは、vector
に格納された重複を含む数値を、sort
で並べ替え、unique
で重複を後ろに集め、.erase
で完全に削除します。
完成コード
#include <iostream>
#include <vector>
#include <algorithm> // sort, unique
using namespace std;
// vectorの内容を表示するヘルパー関数
void print_vector(const string& title, const vector<int>& vec) {
cout << title << ": { ";
for (int val : vec) {
cout << val << " ";
}
cout << "}" << endl;
}
int main() {
vector<int> numbers = {10, 30, 20, 10, 30, 30, 40, 20};
print_vector("初期状態", numbers);
// 1. まず、sort()で要素を並べ替える
sort(numbers.begin(), numbers.end());
print_vector("sort()後", numbers);
// -> { 10 10 20 20 30 30 30 40 }
// 2. unique()で、連続した重複要素を後ろに移動させる
auto last = unique(numbers.begin(), numbers.end());
print_vector("unique()後", numbers);
// -> { 10 20 30 40 | 20 30 30 40 } のような状態になる
// ^ lastイテレータはここを指す
// 3. .erase()で、unique()が返した位置から末尾までを削除する
numbers.erase(last, numbers.end());
print_vector("erase()後", numbers);
// -> { 10 20 30 40 }
return 0;
}
実行結果
初期状態: { 10 30 20 10 30 30 40 20 }
sort()後: { 10 10 20 20 30 30 30 40 }
unique()後: { 10 20 30 40 20 30 30 40 }
erase()後: { 10 20 30 40 }
コードの解説
sort(numbers.begin(), numbers.end());
まずコンテナをソートし、{10, 10, 20, 20, 30, 30, 30, 40}
のように、同じ値が隣り合うようにします。
auto last = unique(numbers.begin(), numbers.end());
unique
は、範囲内の重複した要素を、範囲の末尾に移動させます(値は不定)。そして、重複がなくなった範囲の次の要素を指すイテレータを返します。 unique()
実行後の vector
の中身は { 10, 20, 30, 40, ?, ?, ?, ? }
のようになり(?
の部分は移動してきた元の値)、last
は |
の位置を指すイテレータになります。vector
の size()
は、この時点ではまだ変わりません。
numbers.erase(last, numbers.end());
.erase(開始イテレータ, 終了イテレータ)
は、指定された範囲の要素をコンテナから物理的に削除します。 last
(重複部分の開始位置)から、numbers.end()
(コンテナの末尾)までを削除することで、不要な要素がコンテナから完全に取り除かれます。
まとめ
今回は、C++でコンテナから重複要素を削除するための標準的なイディオム(定石)を解説しました。
std::sort()
でコンテナをソートする。std::unique()
で連続した重複を末尾に移動させ、重複部分の開始イテレータを取得する。- コンテナの
.erase()
メソッドで、unique
が返したイテレータから末尾までを削除する。
この sort-unique-erase
の3ステップは、vector
から重複を削除する際の決まった手順ですので、ぜひ覚えておきましょう。