第56回 変換系アルゴリズム ★目玉
要素を変換/コピー/削除/置換する関数群。Python の map/filter に相当する transform/copy_if、erase-remove イディオム、等を整理します。
このページで押さえること
✅ 最低限
std::transform ― 各要素を変換
std::copy / std::copy_if ― コピー
std::remove + erase ― 削除イディオム
std::fill / std::generate ― 埋める
⭐ 余裕があれば
replace / replace_if
reverse / rotate
unique(連続重複の除去)
- C++20 の
std::erase / erase_if
1. 一覧
| 関数 | 用途 |
transform | 各要素に関数を適用して変換 |
copy, copy_if, copy_n | 範囲コピー(条件付きも可) |
fill, fill_n | 指定値で埋める |
generate | 関数の戻り値で埋める |
replace, replace_if | 値・条件で置換 |
remove, remove_if | 削除(erase と組合せ) |
reverse | 逆順にする |
rotate | 指定位置を先頭にローテート |
unique | 連続重複を除去 |
2. transform ― map 相当
transformmap
std::vector<int> v = {1,2,3,4,5};
std::vector<int> out(v.size());
// 各要素を 2 倍
std::transform(v.begin(), v.end(), out.begin(),
[](int x){ return x * 2; });
// out = {2, 4, 6, 8, 10}
// 2 つの範囲を組み合わせる(ベクトル同士の加算)
std::vector<int> a = {1,2,3};
std::vector<int> b = {10,20,30};
std::vector<int> c(3);
std::transform(a.begin(), a.end(), b.begin(), c.begin(),
[](int x, int y){ return x + y; });
// c = {11, 22, 33}
3. copy_if ― filter 相当
copy_iffilter
std::vector<int> v = {1,2,3,4,5,6};
std::vector<int> evens;
std::copy_if(v.begin(), v.end(),
std::back_inserter(evens),
[](int x){ return x % 2 == 0; });
// evens = {2, 4, 6}
std::back_inserter は「末尾に push_back する疑似イテレータ」。サイズが事前に分からないとき便利。
4. erase-remove イディオム
remove + erase定石
std::vector<int> v = {1,2,3,2,4,2};
// remove は要素を『詰める』だけで、サイズは変えない
auto new_end = std::remove(v.begin(), v.end(), 2);
// v = {1, 3, 4, ?, ?, ?} ← 後ろのゴミをeraseで削除
v.erase(new_end, v.end());
// v = {1, 3, 4}
// 一行で:
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
// C++20 なら 1 発:
std::erase(v, 2); // 値で削除
std::erase_if(v, [](int x){ return x > 3; });
なぜ 2 段階か: STL アルゴリズムはコンテナのサイズを変えられません(アルゴリズムはイテレータだけで動くため)。remove は「残す要素を前詰め」+「末尾の位置を返す」だけ。実際に削除するのは erase。
5. その他の変換
reverse / rotate並び替え
std::reverse(v.begin(), v.end());
// 逆順に
std::rotate(v.begin(),
v.begin() + 2,
v.end());
// {3,4,5,1,2}
fill / generate埋める
std::fill(v.begin(), v.end(), 0);
// 全要素 0
int c = 0;
std::generate(v.begin(), v.end(),
[&c]{ return ++c; });
// {1,2,3,...}
replace置換
std::replace(v.begin(), v.end(), 2, 99);
// 2 を 99 に
std::replace_if(v.begin(), v.end(),
[](int x){ return x < 0; },
0);
// 負の値を 0 に
unique連続重複除去
// 連続する重複だけ除去(先にソート必要)
std::sort(v.begin(), v.end());
v.erase(
std::unique(v.begin(), v.end()),
v.end());
// {1,1,2,2,3} → {1,2,3}
確認クイズ
Q1. vector の全要素を 2 倍にする最も自然な書き方は?
std::sort
std::copy
std::transform
std::replace
transform が「map 相当」。同じ範囲を上書きすることも可能(v.begin() を出力にする)。
Q2. std::remove だけでは vector のサイズが変わらない理由は?
バグ
スレッドセーフのため
アルゴリズムはイテレータしか知らずコンテナを直接操作できないから
コンパイラの都合
STL アルゴリズムはコンテナ非依存なのでサイズを変えられません。実際の削除は erase(コンテナのメソッド)で行います。これが erase-remove イディオム。
Q3. 可変長の出力バッファにコピーするとき使うイテレータは?
v.begin()
std::back_inserter(v)
std::ostream_iterator
v.end()
back_inserter は出力される値を push_back する疑似イテレータ。事前にサイズを確保しなくて済みます。
Q4. std::unique を使うときの注意は?
STL 外の関数
連続する重複しか除去されないので、先にソートが必要
副作用がある
erase 不要
unique は「連続重複」のみを除去します。ソート + unique + erase が「全重複除去」の定石。