第 8 回 | 2024-11-22¶
- ランダムな数を作る方法を学ぶ
- たくさんの値を扱う方法(配列)を学ぶ
- 配列の機能を学ぶ
- 配列の要素の並び替え操作を学ぶ
1. ランダムな数を作る¶
1.1 ランダムな数を生成する¶
<random>
をインクルードすると、乱数生成エンジンの一種であるstd::mt19937
型が使えるようになる。std::mt19937
型の変数は、()
をつけるとランダムな整数を生成する。std::mt19937
は、決定論的な(再現性がある)乱数生成エンジンであり、同一のシード値からは同じ乱数列を生成する。- シード値を指定しなかった場合のデフォルトのシードは
5489
であり、そのシード値からは次の「出力」に示したような乱数列を生成する。
📙 Unit 35
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng;
for (int i = 0; i < 5; ++i)
{
std::cout << rng() << "\n";
}
}
1.2 ランダムな数のシード値¶
std::mt19937 rng{ seed }
によって、明示的にシード値を与えることができる。- シード値が変わると、生成される乱数列も異なるものになる。
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ 1234 };
for (int i = 0; i < 5; ++i)
{
std::cout << rng() << "\n";
}
}
1.3 予測不能なランダムな数¶
- シード値を人間が決めていては、予測不能なランダムな数を生成できない。
- そこで、人間が制御できない予測不能な乱数を生成する
std::random_device
型を使う。 std::random_device{}()
は、ハードウェアノイズ(人間が制御できない予測不能な物理ソース)に基づく非決定論的な乱数値を 1 個生成する。- そうして生成された予測不能な値をシード値とすることで、実行するたびに異なる乱数列を生成するプログラムを作れる。
📙 Unit 36
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ std::random_device{}() };
for (int i = 0; i < 5; ++i)
{
std::cout << rng() << "\n";
}
}
1.4 ランダムな数の範囲¶
- 生成する乱数の範囲を指定したい場合(例えばサイコロであれば 1 から 6 の範囲)、
std::uniform_int_distribution<int>
型を使う。 std::uniform_int_distribution<int> dist{ min, max };
のように範囲を指定する変数を作る。- その変数に
()
を付け、その括弧の中に乱数生成エンジンの変数を入れる。 dist(rng)
とすることで、min
以上max
以下の整数をランダムに生成する。
📙 Unit 37
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ std::random_device{}() };
// 乱数の範囲を 1 以上 6 以下にするための分布変数
std::uniform_int_distribution<int> dist{ 1, 6 };
for (int i = 0; i < 5; ++i)
{
// 分布変数を通して乱数を生成する
std::cout << dist(rng) << "\n";
}
}
1.5 複数のランダムな数の範囲¶
std::uniform_int_distribution<int>
型の変数は、通常の変数と同じように、名前が重複しなければいくつでも作れる。- 一方、乱数生成エンジン
std::mt19937
型の変数は 1 つ用意するだけで十分である。
📙 Unit 38
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ std::random_device{}() };
std::uniform_int_distribution<int> distDice{ 1, 6 };
std::uniform_int_distribution<int> distScore{ 0, 100 };
for (int i = 0; i < 5; ++i)
{
std::cout << distDice(rng) << "\n";
}
for (int i = 0; i < 5; ++i)
{
std::cout << distScore(rng) << "\n";
}
}
1.6 ランダムな選択肢¶
- ランダムな結果(0, 1, ...)に基づいて分岐するプログラムを作ることができる。
📙 Unit 39
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ std::random_device{}() };
std::uniform_int_distribution<int> dist{ 0, 2 };
// n は 0, 1, 2 のいずれか
int n = dist(rng);
if (n == 0)
{
std::cout << "今日はラッキーな1日\n";
}
else if (n == 1)
{
std::cout << "今日はまずまずの1日\n";
}
else
{
std::cout << "今日はダメダメな1日\n";
}
}
1.7 確率の調整¶
- 一定の確率であたりになるようなくじをプログラムで作ってみよう。
- 0 から 99 の範囲でランダムな数を作り、その数が 20 以下であるか、30 以下であるかというように範囲を決めることで、20% の確率や 30% の確率を表現できる。
- お手本のプログラムでは 0〜99 の範囲の 100 種類の整数がランダムに作られる。その中で 0〜19 の 20 種類の数が出た場合に「あたり!」と表示する。あたりになる確率は 100 分の 20, つまり 20% である。
📙 Unit 40
#include <iostream>
#include <random>
int main()
{
std::mt19937 rng{ std::random_device{}() };
std::uniform_int_distribution<int> dist{ 0, 99 };
// n は 0, 1, ..., 99 のいずれか
int n = dist(rng);
if (n < 20) // 20 % の確率で
{
std::cout << "あたり!\n";
}
else
{
std::cout << "はずれ・・・\n";
}
}
2. たくさんの値を扱う¶
2.1 複数の変数¶
- たくさんの値を扱うには、たくさんの変数を作るという方法が考えられるが、これでは大変である。
#include <iostream>
int main()
{
// 店が扱う商品の価格のリスト
int price0 = 100;
int price1 = 400;
int price2 = 300;
int price3 = 200;
int price4 = 200;
}
2.2 配列¶
<vector>
をインクルードすると使えるstd::vector<Type>
型は、1 つの変数でType
型の値をたくさん持つことができる。Type
にはint
やdouble
など、好きな型を指定できる。
std::vector<Type>
のように値の集合を管理できる型を配列といい、配列に含まれる一つ一つの値を配列の要素という。- 例:
std::vector<int>
は、int
型の要素をたくさん持てる配列。 - 例:
std::vector<double>
は、double
型の要素をたくさん持てる配列。 - 例:
std::vector<std::string>
は、std::string
型の要素をたくさん持てる配列。
- 例:
#include <iostream>
#include <vector>
int main()
{
// 店が扱う商品の価格のリスト
std::vector<int> prices = { 100, 400, 300, 200, 200 };
}
- このように、5 つの商品の価格を
prices
という 1 つの変数で管理できる。
2.3 配列の要素数¶
- 配列がいくつの要素を持っているかは、配列変数名に続いて
.size()
を書くことでわかる。- 例:
prices.size()
は、prices
が持っている要素数を返す。
- 例:
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
std::cout << "商品は " << prices.size() << " 個\n";
}
2.4 すべての値を調べる¶
- 配列は、すべての要素にループで順番にアクセスするための特別な文法に対応する。
- 要素の名前は任意である。
- 例えば、
for (const auto& price : prices)
と書くことで、prices
が持っている値をすべて調べることができる。 - この
for
は、prices
の要素数分だけ繰り返し、先頭の要素から順に、繰り返しごとに変数price
を通してアクセスする。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
for (const auto& price : prices)
{
std::cout << price << " 円\n";
}
}
- 次のようなコードと同じはたらきをする。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
for (size_t i = 0; i < prices.size(); ++i)
{
std::cout << prices[i] << " 円\n";
}
}
2.5 合計を求める¶
- 配列のすべての要素を調べて、それらの合計を求めるプログラムを作ることができる。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 合計を記録するための変数
int sum = 0;
for (const auto& price : prices)
{
sum += price;
}
std::cout << "合計: " << sum << " 円\n";
}
2.6 平均を求める¶
- 合計を求めた後に、その合計を要素数で割ることで、平均を求めることができる。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 合計を記録するための変数
int sum = 0;
for (const auto& price : prices)
{
sum += price;
}
std::cout << "合計: " << sum << " 円\n";
std::cout << "平均: " << (sum / prices.size()) << " 円\n";
}
- 小数点以下も含めた平均を求めるためには、
sum
をdouble
型にするとよい。
2.7 要素を検索する¶
- 特定の値を持つ要素の個数を求めることができる。
- 次のコードでは、200 円の商品の個数を調べている。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 検索する値
int search = 200;
// 見つかった個数
int count = 0;
for (const auto& price : prices)
{
if (price == search)
{
++count;
}
}
std::cout << search << " 円の商品は: " << count << " 個\n";
}
2.8 最大の要素を求める¶
- 配列のすべての要素を調べて、最大の要素を求めることができる。
- 最初に暫定の最大値を用意し、それより大きい値が見つかった場合に、その値で更新していく流れになる。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 最大の記録
int max = 0;
for (const auto& price : prices)
{
if (max < price)
{
max = price;
}
}
std::cout << "最も高い商品は: " << max << " 円\n";
}
2.9 double 型の配列¶
double
型の値をたくさん持つ配列はstd::vector<double>
型を使う。
#include <iostream>
#include <vector>
int main()
{
std::vector<double> pumpkins = { 1.6, 1.2, 2.2, 1.9, 2.2 };
for (const auto& pumpkin : pumpkins)
{
std::cout << pumpkin << " kg\n";
}
}
2.10 std::string 型の配列¶
std::string
型の値をたくさん持つ配列はstd::vector<std::string>
型を使う。
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words = { "apple", "bird", "cat", "dog" };
for (const auto& word : words)
{
std::cout << word << "\n";
}
}
3. 配列の機能を学ぶ¶
3.1 配列のインデックス¶
std::vector<Type>
は連続する複数(0 個以上)のType
型の要素から構成される。std::vector<Type>
型の変数は、[index]
を使って、その中の 1 つの要素にアクセスできる。index
は 0 から始まり、要素数 - 1
まである。- 存在しない要素にアクセス(要素数が 6 なのに
v[10]
)すると、実行時エラーが発生する。存在しない要素には絶対にアクセスしてはいけない。
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words = { "apple", "bird", "cat", "dog" };
std::cout << words[0] << "\n"; // apple
std::cout << words[1] << "\n"; // bird
std::cout << words[3] << "\n"; // dog
}
3.2 配列の要素の変更¶
[index]
経由で、配列内の指定した位置の要素を変更できる。
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words = { "apple", "bird", "cat", "dog" };
std::cout << words[2] << "\n"; // cat
words[2] = "computer";
std::cout << words[2] << "\n"; // computer
}
3.3 空の配列¶
std::vector<Type> 配列名;
のように初期値= {...};
を省略した場合は、要素数が 0 である 空(から)の配列を作成する。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices;
std::cout << "商品は " << prices.size() << " 個\n";
for (const auto& price : prices)
{
std::cout << price << " 円\n";
}
}
3.4 要素を末尾に追加する¶
- 配列の末尾に新しい要素 x を追加するときは
配列名.push_back(x);
を使う。 - 次の例で、
prices
は最初は空の配列である。prices.push_back(100);
すると、prices
は{ 100 }
になる。さらにprices.push_back(400);
すると、prices
は{ 100, 400 }
になる。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> prices;
prices.push_back(100);
prices.push_back(400);
std::cout << "商品は " << prices.size() << " 個\n";
for (const auto& price : prices)
{
std::cout << price << " 円\n";
}
}
3.5 先頭の要素を調べる¶
- 配列の先頭の要素は
.front()
でアクセスできる。 words.front()
はwords[0]
と同じである。
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words = { "apple", "bird", "cat", "dog" };
std::cout << words.front() << "\n"; // apple
}
3.6 末尾の要素を調べる¶
- 配列の末尾の要素は
.back()
でアクセスできる。 words.back()
はwords[words.size() - 1]
と同じである。
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> words = { "apple", "bird", "cat", "dog" };
std::cout << words.back() << "\n"; // dog
}
4. 配列の要素の並び替え操作¶
- 配列全体に対して、要素の並び替えを行う機能を学ぶ。
4.1 要素を小さい順に並び替える¶
<algorithm>
をインクルードすると使えるstd::sort()
は、配列の要素を小さい順に並び替える。- 具体的には、
std::sort(配列名.begin(), 配列名.end());
とすると、配列名
の要素が小さい順に並び替えられる。 - 小さい順に並び替えることをソートという。
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 小さい順にソートする
std::sort(prices.begin(), prices.end());
for (const auto& price : prices)
{
std::cout << price << " 円\n";
}
}
- この例では、
prices
は最初{ 100, 400, 300, 200, 200 }
だったが、ソートによって{ 100, 200, 200, 300, 400 }
になった。
4.2 要素を逆順にする¶
<algorithm>
をインクルードすると使えるstd::reverse()
は、配列の要素を逆順に並び替える。- 具体的には、
std::reverse(配列名.begin(), 配列名.end());
とすると、配列名
の要素が逆順に並び替えられる。
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> prices = { 100, 400, 300, 200, 200 };
// 逆順にする
std::reverse(prices.begin(), prices.end());
for (const auto& price : prices)
{
std::cout << price << " 円\n";
}
}
- この例では、
prices
は最初{ 100, 400, 300, 200, 200 }
だったが、逆順にすることで{ 200, 200, 300, 400, 100 }
になった。
✅ 振り返りチェックリスト¶
- ランダムな数を作る方法を学んだ
- たくさんの値を扱う方法(配列)を学んだ
- 配列の機能を学んだ
- 配列の要素の並び替え操作を学んだ
📝 課題¶
- 配列に関する問題を解いてみましょう。
🐣 練習問題¶
農作業で収穫したカボチャの重さ (kg) が配列で用意されます。次の情報を出力するプログラムを作成してください。
- 合計の個数
- 合計の重さ
- 平均の重さ
- 最大の重さ
- 最小の重さ
- カボチャは必ず 1 個以上用意されます。
- 次のプログラムでは
{ 1.6, 1.2, 2.2, 1.9, 2.2 }
のカボチャが用意されていますが、採点時は異なるパターン(例えば{ 3.2, 2.9 }
など) に書き換えられます。パターンが異なる場合でも正しく動作するようにプログラムを作成してください。
解答テンプレート
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// カボチャの重さ (kg)。採点時は別の収穫結果に書き換えられます
std::vector<double> pumpkins = { 1.6, 1.2, 2.2, 1.9, 2.2 };
// これ以降のコードをアレンジして完成させてください
// カボチャの合計の個数
std::cout << "合計の個数: " << 0 << "\n";
// カボチャの合計の重さ
std::cout << "合計の重さ: " << 0.0 << "kg\n";
// カボチャの平均の重さ
std::cout << "平均の重さ: " << 0.0 << "kg\n";
// カボチャの最大の重さ
std::cout << "最大の重さ: " << 0.0 << "kg\n";
// カボチャの最小の重さ
std::cout << "最小の重さ: " << 0.0 << "kg\n";
}
解法 Step 1¶
- 「合計の個数」は
pumpkins.size()
で求めることができます。 - カボチャの「合計の重さ」を求めるために、変数とループを追加します。
- 「平均の重さ」は「合計の重さ」を「合計の個数」で割ることで求めることができます。
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// カボチャの重さ (kg)。採点時は別の収穫結果に書き換えられます
std::vector<double> pumpkins = { 1.6, 1.2, 2.2, 1.9, 2.2 };
// カボチャの合計の重さ
double sum = 0.0;
for (const auto& pumpkin : pumpkins)
{
sum += pumpkin;
}
// カボチャの合計の個数
std::cout << "合計の個数: " << pumpkins.size() << "\n";
// カボチャの合計の重さ
std::cout << "合計の重さ: " << sum << "kg\n";
// カボチャの平均の重さ
std::cout << "平均の重さ: " << (sum / pumpkins.size()) << "kg\n";
// カボチャの最大の重さ
std::cout << "最大の重さ: " << 0.0 << "kg\n";
// カボチャの最小の重さ
std::cout << "最小の重さ: " << 0.0 << "kg\n";
}
解法 Step 2¶
- 最小値や最大値を記録するための変数を追加します。
- 最小値や最大値の初期値は、最初のカボチャの重さ
pumpkins.front()
にしておきます。- 例えば最小値を
0.0
にしておくと、それがそのまま最小値になってしまうためです
- 例えば最小値を
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// カボチャの重さ (kg)。採点時は別の収穫結果に書き換えられます
std::vector<double> pumpkins = { 1.6, 1.2, 2.2, 1.9, 2.2 };
// カボチャの合計の重さ
double sum = 0.0;
// 最大値
double max = pumpkins.front();
// 最小値
double min = pumpkins.front();
for (const auto& pumpkin : pumpkins)
{
sum += pumpkin;
// 現在の最大記録を上回る値があれば、記録を更新する
if (max < pumpkin)
{
max = pumpkin;
}
// 現在の最小記録を下回る値があれば、記録を更新する
if (pumpkin < min)
{
min = pumpkin;
}
}
// カボチャの合計の個数
std::cout << "合計の個数: " << pumpkins.size() << "\n";
// カボチャの合計の重さ
std::cout << "合計の重さ: " << sum << "kg\n";
// カボチャの平均の重さ
std::cout << "平均の重さ: " << (sum / pumpkins.size()) << "kg\n";
// カボチャの最大の重さ
std::cout << "最大の重さ: " << max << "kg\n";
// カボチャの最小の重さ
std::cout << "最小の重さ: " << min << "kg\n";
}
別解¶
- 配列をソートすると、先頭に最小値、末尾に最大値が来るため、最小値や最大値を簡単に調べることができます。
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// カボチャの重さ (kg)。採点時は別の収穫結果に書き換えられます
std::vector<double> pumpkins = { 1.6, 1.2, 2.2, 1.9, 2.2 };
// 小さい順に並ぶようソートする
std::sort(pumpkins.begin(), pumpkins.end());
double sum = 0.0;
for (const auto& pumpkin : pumpkins)
{
sum += pumpkin;
}
// カボチャの合計の個数
std::cout << "合計の個数: " << pumpkins.size() << "\n";
// カボチャの合計の重さ
std::cout << "合計の重さ: " << sum << "kg\n";
// カボチャの平均の重さ
std::cout << "平均の重さ: " << (sum / pumpkins.size()) << "kg\n";
// カボチャの最大の重さ
std::cout << "最大の重さ: " << pumpkins.back() << "kg\n";
// カボチャの最小の重さ
std::cout << "最小の重さ: " << pumpkins.front() << "kg\n";
}
📝 提出課題¶
ボランティア団体の構成員の年齢が配列で用意されます。次の情報を出力するプログラムを作成してください。
- 最年少の年齢
- 最年長の年齢
- 年齢の中央値(小数以下も含めた値)
- 平均年齢(小数以下も含めた値)
- 未成年(18 歳未満)の人数
- 成人(18 歳以上)の人数
- 最も多い年齢(複数ある場合はそのうち一つ)の人数
- 人数は必ず 1 人以上用意されます。
- 次のプログラムでは
{ 20, 17, 22, 20, 18, 20, 19, 18, 20, 21 }
の年齢が用意されていますが、採点時は異なるパターン(例えば{ 30, 10, 50, 10, 17, 18 }
など) に書き換えられます。パターンが異なる場合でも正しく動作するようにプログラムを作成してください。
解答テンプレート
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// 年齢(採点時は別のパターンに書き換えられます)
std::vector<int> ages = { 20, 17, 22, 20, 18, 20, 19, 18, 20, 21 };
// これ以降のコードをアレンジして完成させてください
// 最年少の人の年齢
std::cout << "最年少: " << 0 << "\n";
// 最年長の人の年齢
std::cout << "最年長: " << 0 << "\n";
// 年齢の中央値
std::cout << "中央値: " << 0.0 << "\n";
// 平均年齢
std::cout << "平均年齢: " << 0.0 << "\n";
// 未成年の人数
std::cout << "未成年の人数: " << 0 << "\n";
// 成人の人数
std::cout << "成人の人数: " << 0 << "\n";
// 最も多い年齢の人数
std::cout << "最も多い年齢の人数: " << 0 << "\n";
}
- コードまたはコードの共有 URL(Wandbox)を 1 つだけ提出してください。
パターン例¶
パターン 1
パターン 2
- 人数が偶数の場合、中央値は中央の 2 つの値の平均となります。
パターン 3
パターン 4
第 9 回は 🛜 オンデマンド講義
次週は出張のためオンデマンド講義です。講義ページを自分で読み進め、いつもどおり課題を提出してください。講義のライブ配信や動画はありません。教室は開室しています。講義日に Moodle で質問をすると、翌日までにスピード対応します。