C++ Learning

第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 が「全重複除去」の定石。
← 前の講座
第55回 探索系