|
| 1 | +# 第10章: 標準テンプレートライブラリ (STL) ②:アルゴリズムとラムダ式 |
| 2 | + |
| 3 | +ようこそ、C++チュートリアルの第10章へ!前の章ではSTLのコンテナについて学び、様々なデータを効率的に格納する方法を見てきました。しかし、データを格納するだけではプログラムは完成しません。そのデータを並べ替えたり、検索したり、特定の処理を施したりといった「操作」が必要です。 |
| 4 | + |
| 5 | +この章では、STLのもう一つの強力な柱である**アルゴリズム**ライブラリを学びます。これらのアルゴリズムは、コンテナ内のデータを操作するための汎用的な関数群です。そして、アルゴリズムをさらに柔軟かつ強力にするための現代的なC++の機能、**ラムダ式**についても解説します。これらをマスターすれば、驚くほど少ないコードで複雑なデータ操作が実現できるようになります。 |
| 6 | + |
| 7 | +## イテレータ:コンテナとアルゴリズムを繋ぐ架け橋 |
| 8 | + |
| 9 | +アルゴリズムは、特定のコンテナ(`std::vector` や `std::list` など)に直接依存しないように設計されています。では、どうやってコンテナ内の要素にアクセスするのでしょうか?そこで登場するのが**イテレータ (Iterator)** です。 |
| 10 | + |
| 11 | +イテレータは、コンテナ内の要素を指し示す「ポインタのような」オブジェクトです。ポインタのように `*` で要素の値を参照したり、`++` で次の要素に進んだりできます。 |
| 12 | + |
| 13 | +ほとんどのコンテナは、以下の2つの重要なイテレータを取得するメンバ関数を持っています。 |
| 14 | + |
| 15 | + * `begin()`: コンテナの先頭要素を指すイテレータを返す。 |
| 16 | + * `end()`: コンテナの**最後の要素の次**を指すイテレータを返す。これは有効な要素を指していない「番兵」のような役割を果たします。 |
| 17 | + |
| 18 | +アルゴリズムは、この `begin()` と `end()` から得られるイテレータのペアを使い、操作対象の「範囲」を指定します。範囲は半開区間 `[begin, end)` で表され、`begin` が指す要素は範囲に含まれ、`end` が指す要素は含まれません。 |
| 19 | + |
| 20 | +簡単な例を見てみましょう。イテレータを使って `vector` の全要素を表示するコードです。 |
| 21 | + |
| 22 | +```cpp:iterator_example.cpp |
| 23 | +#include <iostream> |
| 24 | +#include <vector> |
| 25 | + |
| 26 | +int main() { |
| 27 | + std::vector<int> numbers = {0, 1, 2, 3, 4}; |
| 28 | + |
| 29 | + // イテレータを使ってコンテナを走査 |
| 30 | + std::cout << "Numbers: "; |
| 31 | + for (auto it = numbers.begin(); it != numbers.end(); ++it) { |
| 32 | + std::cout << *it << " "; // *it で要素の値にアクセス |
| 33 | + } |
| 34 | + std::cout << std::endl; |
| 35 | + |
| 36 | + // C++11以降の範囲ベースforループ (内部ではイテレータが使われている) |
| 37 | + std::cout << "Numbers (range-based for): "; |
| 38 | + for (int num : numbers) { |
| 39 | + std::cout << num << " "; |
| 40 | + } |
| 41 | + std::cout << std::endl; |
| 42 | + |
| 43 | + return 0; |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +```cpp-exec:iterator_example.cpp |
| 48 | +Numbers: 0 1 2 3 4 |
| 49 | +Numbers (range-based for): 0 1 2 3 4 |
| 50 | +``` |
| 51 | +
|
| 52 | +このように、イテレータはコンテナの種類を問わず、統一的な方法で要素にアクセスする仕組みを提供します。これが、アルゴリズムが様々なコンテナに対して汎用的に機能する理由です。 |
| 53 | +
|
| 54 | +## 便利なアルゴリズム |
| 55 | +
|
| 56 | +C++の標準ライブラリには、`<algorithm>` ヘッダと `<numeric>` ヘッダに数多くの便利なアルゴリズムが用意されています。ここでは、特によく使われるものをいくつか紹介します。 |
| 57 | +
|
| 58 | +### `std::sort`: 要素を並べ替える |
| 59 | +
|
| 60 | +名前の通り、指定された範囲の要素をソートします。デフォルトでは昇順に並べ替えます。 |
| 61 | +
|
| 62 | +```cpp:sort_example.cpp |
| 63 | +#include <iostream> |
| 64 | +#include <vector> |
| 65 | +#include <algorithm> // std::sort のために必要 |
| 66 | +#include <string> |
| 67 | +
|
| 68 | +int main() { |
| 69 | + std::vector<int> numbers = {5, 2, 8, 1, 9}; |
| 70 | + |
| 71 | + // numbers.begin() から numbers.end() の範囲をソート |
| 72 | + std::sort(numbers.begin(), numbers.end()); |
| 73 | +
|
| 74 | + std::cout << "Sorted numbers: "; |
| 75 | + for (int num : numbers) { |
| 76 | + std::cout << num << " "; |
| 77 | + } |
| 78 | + std::cout << std::endl; |
| 79 | +
|
| 80 | + std::vector<std::string> words = {"banana", "apple", "cherry"}; |
| 81 | + std::sort(words.begin(), words.end()); |
| 82 | +
|
| 83 | + std::cout << "Sorted words: "; |
| 84 | + for (const auto& word : words) { |
| 85 | + std::cout << word << " "; |
| 86 | + } |
| 87 | + std::cout << std::endl; |
| 88 | +
|
| 89 | + return 0; |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +```cpp-exec:sort_example.cpp |
| 94 | +Sorted numbers: 1 2 5 8 9 |
| 95 | +Sorted words: apple banana cherry |
| 96 | +``` |
| 97 | + |
| 98 | +### `std::find`: 要素を検索する |
| 99 | + |
| 100 | +指定された範囲から特定の値を持つ要素を検索します。 |
| 101 | + |
| 102 | + * **見つかった場合**: その要素を指すイテレータを返します。 |
| 103 | + * **見つからなかった場合**: 範囲の終端を示すイテレータ (`end()`) を返します。 |
| 104 | + |
| 105 | +この性質を利用して、要素が存在するかどうかをチェックできます。 |
| 106 | + |
| 107 | +```cpp:find_example.cpp |
| 108 | +#include <iostream> |
| 109 | +#include <vector> |
| 110 | +#include <algorithm> // std::find のために必要 |
| 111 | + |
| 112 | +int main() { |
| 113 | + std::vector<int> numbers = {10, 20, 30, 40, 50}; |
| 114 | + int value_to_find = 30; |
| 115 | + |
| 116 | + // numbers の中から 30 を探す |
| 117 | + auto it = std::find(numbers.begin(), numbers.end(), value_to_find); |
| 118 | + |
| 119 | + if (it != numbers.end()) { |
| 120 | + // 見つかった場合 |
| 121 | + std::cout << "Found " << *it << " at index " << std::distance(numbers.begin(), it) << std::endl; |
| 122 | + } else { |
| 123 | + // 見つからなかった場合 |
| 124 | + std::cout << value_to_find << " not found." << std::endl; |
| 125 | + } |
| 126 | + |
| 127 | + value_to_find = 99; |
| 128 | + it = std::find(numbers.begin(), numbers.end(), value_to_find); |
| 129 | + |
| 130 | + if (it != numbers.end()) { |
| 131 | + std::cout << "Found " << *it << std::endl; |
| 132 | + } else { |
| 133 | + std::cout << value_to_find << " not found." << std::endl; |
| 134 | + } |
| 135 | + |
| 136 | + return 0; |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +```cpp-exec:find_example.cpp |
| 141 | +Found 30 at index 2 |
| 142 | +99 not found. |
| 143 | +``` |
| 144 | + |
| 145 | +### `std::for_each`: 各要素に処理を適用する |
| 146 | + |
| 147 | +指定された範囲の全ての要素に対して、特定の関数(処理)を適用します。ループを書くよりも意図が明確になる場合があります。 |
| 148 | + |
| 149 | +```cpp |
| 150 | +// 3番目の引数に関数を渡す |
| 151 | +std::for_each(numbers.begin(), numbers.end(), print_function); |
| 152 | +``` |
| 153 | +
|
| 154 | +ここで「特定の処理」をその場で手軽に記述する方法が**ラムダ式**です。 |
| 155 | +
|
| 156 | +## ラムダ式:その場で書ける無名関数 |
| 157 | +
|
| 158 | +ラムダ式(Lambda Expression)は、C++11から導入された非常に強力な機能です。一言で言えば、「**その場で定義して使える名前のない小さな関数**」です。これにより、アルゴリズムに渡すためだけの短い関数をわざわざ定義する必要がなくなり、コードが非常に簡潔になります。 |
| 159 | +
|
| 160 | +ラムダ式の基本的な構文は以下の通りです。 |
| 161 | +
|
| 162 | +```cpp |
| 163 | +[キャプチャ](引数リスト) -> 戻り値の型 { 処理本体 } |
| 164 | +``` |
| 165 | + |
| 166 | + * `[]` **キャプチャ句**: ラムダ式の外にある変数を取り込んで、式の中で使えるようにします。 |
| 167 | + * `[]`: 何もキャプチャしない。 |
| 168 | + * `[=]`: 外の変数を全て値渡し(コピー)でキャプチャする。 |
| 169 | + * `[&]`: 外の変数を全て参照渡しでキャプチャする。 |
| 170 | + * `[x, &y]`: 変数 `x` は値渡し、変数 `y` は参照渡しでキャプチャする。 |
| 171 | + * `()` **引数リスト**:通常の関数と同じ引数を取ることができます。 |
| 172 | + * `-> 戻り値の型`: 戻り値の型を指定します。多くの場合、コンパイラが推論できるため省略可能です。 |
| 173 | + * `{}` **処理本体**: 関数の処理内容を記述します。 |
| 174 | + |
| 175 | +`std::for_each` とラムダ式を組み合わせた例を見てみましょう。 |
| 176 | + |
| 177 | +```cpp:for_each_lambda_example.cpp |
| 178 | +#include <iostream> |
| 179 | +#include <vector> |
| 180 | +#include <algorithm> |
| 181 | + |
| 182 | +int main() { |
| 183 | + std::vector<int> numbers = {1, 2, 3, 4, 5}; |
| 184 | + |
| 185 | + // 各要素を2倍して表示する |
| 186 | + std::cout << "Doubled numbers: "; |
| 187 | + std::for_each(numbers.begin(), numbers.end(), [](int n) { |
| 188 | + std::cout << n * 2 << " "; |
| 189 | + }); |
| 190 | + std::cout << std::endl; |
| 191 | + |
| 192 | + // 外部の変数をキャプチャする例 |
| 193 | + int sum = 0; |
| 194 | + // `&sum` で sum を参照キャプチャし、ラムダ式内で変更できるようにする |
| 195 | + std::for_each(numbers.begin(), numbers.end(), [&sum](int n) { |
| 196 | + sum += n; |
| 197 | + }); |
| 198 | + |
| 199 | + std::cout << "Sum: " << sum << std::endl; |
| 200 | + |
| 201 | + return 0; |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +```cpp-exec:for_each_lambda_example.cpp |
| 206 | +Doubled numbers: 2 4 6 8 10 |
| 207 | +Sum: 15 |
| 208 | +``` |
| 209 | + |
| 210 | +このコードは、`for_each` の3番目の引数に直接処理を書き込んでいます。非常に直感的で読みやすいと思いませんか? |
| 211 | + |
| 212 | +ラムダ式は、特に `std::sort` のソート順をカスタマイズする際に真価を発揮します。例えば、数値を降順にソートしたい場合、比較ルールをラムダ式で与えることができます。 |
| 213 | + |
| 214 | +```cpp:lambda_sort_example.cpp |
| 215 | +#include <iostream> |
| 216 | +#include <vector> |
| 217 | +#include <algorithm> |
| 218 | + |
| 219 | +int main() { |
| 220 | + std::vector<int> numbers = {5, 2, 8, 1, 9}; |
| 221 | + |
| 222 | + // 比較関数としてラムダ式を渡す |
| 223 | + // a > b であれば true を返すことで降順ソートになる |
| 224 | + std::sort(numbers.begin(), numbers.end(), [](int a, int b) { |
| 225 | + return a > b; |
| 226 | + }); |
| 227 | + |
| 228 | + std::cout << "Sorted in descending order: "; |
| 229 | + for (int num : numbers) { |
| 230 | + std::cout << num << " "; |
| 231 | + } |
| 232 | + std::cout << std::endl; |
| 233 | + |
| 234 | + return 0; |
| 235 | +} |
| 236 | +``` |
| 237 | + |
| 238 | +```cpp-exec:lambda_sort_example.cpp |
| 239 | +Sorted in descending order: 9 8 5 2 1 |
| 240 | +``` |
| 241 | + |
| 242 | +## この章のまとめ |
| 243 | + |
| 244 | +この章では、STLのアルゴリズムとラムダ式について学びました。 |
| 245 | + |
| 246 | + * **イテレータ**は、コンテナの要素を指し示すオブジェクトであり、アルゴリズムとコンテナの間のインターフェースとして機能します。 |
| 247 | + * `<algorithm>` ヘッダには、**`std::sort`** (ソート)、**`std::find`** (検索)、**`std::for_each`** (繰り返し処理) といった、汎用的で強力なアルゴリズムが多数用意されています。 |
| 248 | + * **ラムダ式**は、その場で定義できる無名関数であり、アルゴリズムに渡す処理を簡潔かつ直感的に記述することができます。 |
| 249 | + * **キャプチャ**機能を使うことで、ラムダ式の外にある変数を取り込んで処理に利用できます。 |
| 250 | + |
| 251 | +コンテナ、イテレータ、アルゴリズム、そしてラムダ式。これらを組み合わせることで、C++におけるデータ処理は、他の多くの言語に引けを取らない、あるいはそれ以上に表現力豊かで効率的なものになります。 |
| 252 | + |
| 253 | +### 練習問題1: 文字列の長さでソート |
| 254 | + |
| 255 | +`std::vector<std::string>` を用意し、格納されている文字列を、文字数が短い順にソートして、結果を出力するプログラムを作成してください。`std::sort` とラムダ式を使用してください。 |
| 256 | + |
| 257 | +**ヒント**: ラムダ式は2つの文字列を引数に取り、1つ目の文字列の長さが2つ目の文字列の長さより短い場合に `true` を返すように実装します。 |
| 258 | + |
| 259 | + |
| 260 | +```cpp:practice10_1.cpp |
| 261 | +#include <iostream> |
| 262 | +#include <vector> |
| 263 | +#include <algorithm> |
| 264 | + |
| 265 | +int main() { |
| 266 | + std::vector<std::string> words = {"apple", "banana", "kiwi", "cherry", "fig", "grape"}; |
| 267 | + |
| 268 | + return 0; |
| 269 | +} |
| 270 | +``` |
| 271 | + |
| 272 | +```cpp-exec:practice10_1.cpp |
| 273 | +fig kiwi grape apple banana cherry |
| 274 | +``` |
| 275 | + |
| 276 | +### 練習問題2: 条件に合う要素のカウント |
| 277 | + |
| 278 | +`std::vector<int>` に整数をいくつか格納します。その後、ラムダ式と `std::for_each`(または他のアルゴリズム)を使って、以下の2つの条件を満たす要素がそれぞれいくつあるかを数えて出力してください。 |
| 279 | + |
| 280 | +1. 正の偶数である要素の数 |
| 281 | +2. 負の奇数である要素の数 |
| 282 | + |
| 283 | +**ヒント**: カウント用の変数を2つ用意し、ラムダ式のキャプチャ句で参照キャプチャ (`[&]`) して、式の中でインクリメントします。 |
| 284 | + |
| 285 | +```cpp:practice10_2.cpp |
| 286 | +#include <iostream> |
| 287 | +#include <vector> |
| 288 | +#include <algorithm> |
| 289 | + |
| 290 | +int main() { |
| 291 | + std::vector<int> numbers = {3, -1, 4, -5, 6, -7, 8, 0, -2}; |
| 292 | + |
| 293 | + |
| 294 | + return 0; |
| 295 | +} |
| 296 | +``` |
| 297 | + |
| 298 | +```cpp-exec:practice10_2.cpp |
| 299 | +Positive even count: 3 |
| 300 | +Negative odd count: 3 |
| 301 | +``` |
0 commit comments