コンテンツにスキップ

第 9 回 | 2024-11-29 🛜 オンデマンド

第 9 回は 🛜 オンデマンド講義

講義ページを自分で読み進め、いつもどおり課題を提出してください。講義のライブ配信や動画はありません。教室は開室しています。講義日に Moodle で質問をすると、翌日までにスピード対応します。

  • for ループを使って要素を変更する方法を学ぶ
  • while ループを学ぶ
  • switch 文を学ぶ
  • 辞書形式のデータ(ハッシュテーブル)を学ぶ
  • std::getline を使うときの注意について学ぶ
  • プログラミングコンテストについて学ぶ

1. 配列や文字列

1.1 配列のすべての要素を調べる(復習)

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> prices = { 100, 400, 300, 200, 200 };

	for (const auto& price : prices)
	{
		std::cout << price << " 円\n";
	}
}
出力
100 円
400 円
300 円
200 円
200 円


#include <iostream>
#include <vector>

int main()
{
	std::vector<double> pumpkins = { 1.6, 0.9, 2.2 };

	for (const auto& pumpkin : pumpkins)
	{
		std::cout << pumpkin << " kg\n";
	}
}
出力
1.6 kg
0.9 kg
2.2 kg


#include <iostream>
#include <vector>
#include <string>

int main()
{
	std::vector<std::string> words = { "dog", "cat", "bird" };

	for (const auto& word : words)
	{
		std::cout << word << '\n';
	}
}
出力
dog
cat
bird

1.2 文字列のすべての要素を調べる

  • 文字列 (std::string) も配列の一種。
  • std::vector<Type> と同様に、for (const auto& 要素 : 配列) による繰り返しを使って、すべての要素(文字)を調べることができる。

#include <iostream>
#include <string>

int main()
{
	std::string s = "Hello";

	for (const auto& ch : s)
	{
		std::cout << ch << '\n';
	}
}
出力
H
e
l
l
o

1.3 配列の要素を for の中で変更する

  • const を外すことで、配列の要素を変更できるようになる。
  • for (auto& 要素 : 配列) による繰り返しを使って、配列の要素を変更することができる。

#include <iostream>
#include <vector>

int main()
{
	// 商品の値段の配列
	std::vector<int> prices = { 100, 400, 300, 200, 200 };

	// 全ての商品について 50 円値上げする
	for (auto& price : prices)
	{
		price += 50;
	}

	// 値段を表示する(ここでは変更はしないので const)
	for (const auto& price : prices)
	{
		std::cout << price << " 円\n";
	}
}
出力
150 円
450 円
350 円
250 円
250 円

1.4 文字列の要素を for の中で変更する

  • for (auto& 要素 : 配列) による繰り返しを使って、文字列の要素を変更することができる。

#include <iostream>
#include <string>

int main()
{
	std::string s = "Hello";

	for (auto& ch : s)
	{
		ch = 'a';
	}

    std::cout << s << '\n';
}
出力
aaaaa

2. while ループ

2.1 while ループ

  • while (続ける条件) は、「続ける条件」を満たしている間、それに続く { } の中を繰り返し実行する。
  • 文法的には for の簡易版であり、for で代替可能なので無理に使う必要はない。
  • 次のコードでは、n0 より大きい間は繰り返し、n0 以下になったら繰り返しを終了する。これにより、n の各桁の数字を順番に得ることができる。

#include <iostream>

int main()
{
	int n = 3776;

	while (0 < n)
	{
		std::cout << (n % 10) << '\n';

		n /= 10;
	}
}
出力
6
7
7
3

3. switch 文

3.1 switch

  • switch()() 内の整数値に応じて、対応する case の処理を行う。
  • case の処理は、それぞれ break; で終了する。
  • switch は、一連の if - else if でも代替できる。
#include <iostream>

int main()
{
	std::cout << "Where is the capital of South Korea?\n";
	std::cout << "[1] Tokyo [2] Seoul [3] Beijing [4] New Delhi\n";

	int answer;
	std::cin >> answer;

	switch (answer)
	{
	case 1:
		{
			std::cout << "Tokyo is the capital of Japan.\n";
			break;
		}
	case 2:
		{
			std::cout << "Right!\n";
			break;
		}
	case 3:
		{
			std::cout << "Beijing is the capital of China.\n";
			break;
		}
	case 4:
		{
			std::cout << "New Delhi is the capital of India.\n";
			break;
		}
	}
}

break 忘れに注意

  • case の最後の break を忘れると、それに続く次の case の内容まで実行してしまう。

#include <iostream>

int main()
{
	std::cout << "Where is the capital of South Korea?\n";
	std::cout << "[1] Tokyo [2] Seoul [3] Beijing [4] New Delhi\n";

	int answer;
	std::cin >> answer;

	switch (answer)
	{
	case 1:
		{
			std::cout << "Tokyo is the capital of Japan.\n";
		}
	case 2:
		{
			std::cout << "Right!\n";
		}
	case 3:
		{
			std::cout << "Beijing is the capital of China.\n";
		}
	case 4:
		{
			std::cout << "New Delhi is the capital of India.\n";
		}
	}
}
入力
2
出力
Right!
Beijing is the capital of China.
New Delhi is the capital of India.

3.2 switch 文の default

  • switchdefault: を用意すると、対応する case が無いときに、default: の処理を行う。
#include <iostream>

int main()
{
	std::cout << "Where is the capital of South Korea?\n";
	std::cout << "[1] Tokyo [2] Seoul [3] Beijing [4] New Delhi\n";

	int answer;
	std::cin >> answer;

	switch (answer)
	{
	case 1:
		{
			std::cout << "Tokyo is the capital of Japan.\n";
			break;
		}
	case 2:
		{
			std::cout << "Right!\n";
			break;
		}
	case 3:
		{
			std::cout << "Beijing is the capital of China.\n";
			break;
		}
	case 4:
		{
			std::cout << "New Delhi is the capital of India.\n";
			break;
		}
	default:
		{
			std::cout << "Wrong input!\n";
			break;
		}
	}
}

4. 辞書形式のデータ(ハッシュテーブル)

「キー」と「値」のペアがたくさん並ぶデータを、辞書形式のデータという。辞書形式のデータは、キーを使って値の検索や登録、削除などの操作を行うことができる。辞書形式のデータで扱える情報の例に、次のようなものがある。

  • 英和辞典
    • キーが英単語、値が日本語
  • 学生データ
    • キーが学籍番号、値が入学年度
  • 郵便番号
    • キーが郵便番号、値が町名

辞書形式のデータにおいて、検索のアルゴリズムに「ハッシュ」という技術を用いるものを、とくにハッシュテーブルという。

4.1 要素を登録する

  • C++ では std::unordered_map<Key, Value> 型を使ってハッシュテーブルを扱う。
  • <unordered_map> のインクルードが必要。
  • Key には任意のキーの型、Value には任意の値の型を指定する。
  • (例 1)キーが文字列、値が整数: std::unordered_map<std::string, int>
  • (例 2)キーが文字列、値も文字列: std::unordered_map<std::string, std::string>
  • (例 3)キーが整数、値も整数: std::unordered_map<int, int>

  • std::unordered_map<Key, Value> 型の変数には、変数名[key] = value; の形式で要素を登録する。
  • ここでの key, value はそれぞれ最初に指定した Key 型、Value 型に沿った値。
  • 1 つのハッシュテーブルには、同じキーを持つ要素は 1 つしか登録できない。
  • 同じキーで要素を登録すると、以前の要素の値は上書きされる。
書店における、本のジャンルとフロアの対応をハッシュテーブルで表現
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	// 要素を登録する
	table["computer"] = 3;

	// 要素を登録する
	table["sports"] = 2;

	// 要素を登録する
	table["comic"] = 1;

	// 要素を登録する
	table["cooking"] = 2;
}
  • 標準入力を使って同じような処理を書くと、次のようになる。
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	// キーと値を標準入力
	std::string key;
	std::cin >> key;
	
	int value;
	std::cin >> value;

	// 入力に基づいて要素を登録する
	table[key] = value;
}

4.2 要素の個数

  • ハッシュテーブルに登録されている要素(値とキーのペア)の個数は .size() で取得できる。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	std::cout << table.size() << " ジャンル\n";
}
出力
4 ジャンル

4.3 値を調べる

  • 変数名[key] で、キー key に対応する値を検索して取得できる。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	// 指定したキーの値を調べる
	std::cout << table["sports"] << " 階\n";
}
出力
2 階

  • 標準入力を使って同じような処理を書くと、次のようになる。
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	// キーを標準入力
	std::string key;
	std::cin >> key;

	// 指定したキーの値を調べる
	std::cout << table[key] << " 階\n";
}

4.4 値を調べるときの注意

  • 登録されていないキーを [] を使って検索すると、そのキーの要素が新規作成され、値として Value 型のデフォルト値 (int の場合は 0) が自動登録される仕様になっている。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	// 登録されていないキー "art" を検索したため、
	// table["art"] = 0; が自動登録される
	std::cout << table["art"] << " 階\n";

	std::cout << table.size() << " ジャンル\n";
}
出力
0 階
5 ジャンル

  • このような副作用を回避するため、4.5 のように、キーが登録されているかを先に調べるようにする。

4.5 キーが登録されているかを調べる

  • 4.4 のような副作用を回避するため、ハッシュテーブルに特定のキーが登録されているかを先に調べる。
  • table.find(key) は、キー key が登録されている場合はその要素へのイテレータを返し、登録されていない場合は table.end() を返す。
  • つまり if (table.find(key) == table.end()) は、キー key が登録されていない場合に true になる。
  • キーが登録されていない場合は、[] を使った検索を行わないようにする。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	std::string search = "art";

	// 指定したキーの要素が登録されているかを調べる
	if (table.find(search) == table.end()) // 登録されていない
	{
		std::cout << search << " の本は存在しない\n";
	}
	else // 登録されている
	{
		std::cout << table[search] << " 階\n";
	}

	std::cout << table.size() << " ジャンル\n";
}
出力
art の本は存在しない
4 ジャンル

4.6 値を変更する

  • 1 つのハッシュテーブルには、同じキーを持つ要素は 1 つまでしか登録できない。
  • 変数名[key] = value; は、すでにキー key が登録されている場合は、その値を value で上書きする。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	// 値を上書きする
	table["cooking"] = 10;

	std::cout << table["cooking"] << " 階\n";
}
出力
10 階

4.7 要素をすべて表示する

  • for (auto&& [key, value] : table) によるループで、すべての要素にアクセスできる。key がキー、value が値にあたる。
  • 要素の順序は登録順とは無関係。これはハッシュテーブルの仕様。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	for (auto&& [key, value] : table)
	{
		std::cout << key << " の本は " << value << " 階\n";
	}
}
出力例
cooking の本は 2 階
comic の本は 1 階
computer の本は 3 階
sports の本は 2 階

C++ のバージョンが古いと動かない

  • for (auto&& [key, value] : table) によるループは、C++17 以降でのみ動作する。replitWandbox はデフォルトで C++17 に対応するため、何もしなくても動作する。
  • Visual Studio 2022 では「プロジェクト」→「(プロジェクト名)のプロパティ」を開くと出てくるウィンドウで、「構成(C):」を「すべての構成」にしたうえで、同じウィンドウ内の「構成プロパティ」→「全般」にある「C++ 言語標準」を「ISO C++14 標準 (/std:c++14)」から「ISO C++17 標準 (/std:c++17)」に変更することでコンパイルできる。

4.8 要素を削除する

  • キー key の要素を削除する場合は 変数名.erase(key) を使う。
  • 存在しないキーを削除しようとしたときは、何も起こらない。

#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 本のジャンル
	// 値: int - フロア
	std::unordered_map<std::string, int> table;

	table["computer"] = 3;

	table["sports"] = 2;

	table["comic"] = 1;

	table["cooking"] = 2;

	// キーが "sports" の要素を削除
	table.erase("sports");

	for (auto&& [key, value] : table)
	{
		std::cout << key << " の本は " << value << " 階\n";
	}
}
出力
cooking の本は 2 階
comic の本は 1 階
computer の本は 3 階

5. std::getline を使うときの注意

5.1 std::getline の注意

  • 第 7 回で学んだ std::getline() は、通常の std::cin と併用するときに注意すべき動作がある。

#include <iostream>
#include <string>

int main()
{
	int a;
	std::cin >> a;

	std::string b;
	std::getline(std::cin, b);

	std::cout << "a: " << a << '\n';
	std::cout << "b: " << b << '\n';
}
入力
123
Hello
出力
a: 123
b: 

  • これは、入力の 123 の直後の改行 '\n' がまだ残った状態で std::getline() が呼ばれるため、その改行を受け取って std::getline() が満足してしまうため起こる。
  • これを防ぐため、std::cin >> による数値入力と std::getline() を混在させる場合、数値入力の直後に std::cin.ignore(1000, '\n'); を追加して、改行を捨てるようにする。
  • std::cin.ignore(1000, '\n'); は最大 1000 文字まで、改行文字 '\n' が現れる場所まで読み飛ばす。これにより何も残っていない状態で次の std::getline() が呼ばれるため、正しく動作するようになる。

#include <iostream>
#include <string>

int main()
{
	int a;
	std::cin >> a;
	std::cin.ignore(1000, '\n');

	std::string b;
	std::getline(std::cin, b);

	std::cout << "a: " << a << '\n';
	std::cout << "b: " << b << '\n';
}
入力
123
Hello
出力
a: 123
b: Hello

6. プログラミングコンテスト

✅ 振り返りチェックリスト

  • for ループを使って要素を変更する方法を学んだ
  • while ループを学んだ
  • switch 文を学んだ
  • 辞書形式のデータ(ハッシュテーブル)を学んだ
  • std::getline を使うときの注意について学んだ
  • プログラミングコンテストについて学んだ

📝 課題

🐣 練習問題

ユーザーが大学の施設の名前を入力すると、その場所を教えてくれる案内プログラムを、ハッシュテーブルを用いて作成してください。

何回でも施設名を入力できるようにし、exit と入力されたらプログラムを終了するようにしてください。

ヒント
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// ...

	for (;;)
	{
		std::cout << "施設名を入力してください。終了する場合は exit と入力してください\n";
		std::string search;
		std::getline(std::cin, search);

		if (search == "exit")
		{
			break;
		}


	}
}
解答例
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// キー: std::string - 施設名
	// 値: std::string - 場所
	std::unordered_map<std::string, std::string> table;
	table["restaurant"] = "2 号館 5F, 11 号館 B1F, ホフマンホール 4F にあります。";
	table["campus store"] = "2 号館 B1F にあります。";

	for (;;)
	{
		std::cout << "施設名を入力してください。終了する場合は exit と入力してください\n";
		std::string search;
		std::getline(std::cin, search);

		if (search == "exit")
		{
			break;
		}

		if (table.find(search) == table.end())
		{
			std::cout << search << " はデータベースに存在しません。\n";
		}
		else
		{
			std::cout << search << " は " << table[search] << '\n';
		}
	}
}

📝 提出課題

キーボード入力(標準入力)を通して要素の登録検索一覧表示削除ができる、何らかのデータベースを、ハッシュテーブルを用いて作ってください。キー、値をどのような型・内容にするかは自由です。

データベースには最初からいくつかの要素が登録されているようにしてください。また、データベースの利用者が使いやすいように、操作方法を出力するなどの工夫をしてください。

趣味に関連したデータベースを作ると、より楽しく作業できるかもしれません。

  • 本とその著者 std::unordered_map<std::string, std::string>
  • ゲームとその発売年 std::unordered_map<std::string, int>
  • アニメとその放送年 std::unordered_map<std::string, int>
  • 映画とその監督
  • 音楽とそのアーティスト
  • 食べ物とそのカロリー
  • スポーツチームとその本拠地
  • 旅行先とその観光名所

コードまたはコードの共有 URL(Wandbox)を提出してください。

評価要素

  • データベースの利用開始時点で要素が最初からいくつか登録されている
  • データベースの要素の一覧表示ができる
  • データベースで要素の検索ができる
  • データベースに要素の新規登録ができる
  • データベースから要素の削除ができる
  • その他の発展的な機能を考えて実装した場合は加点
ヒント
#include <iostream>
#include <string>
#include <unordered_map>

int main()
{
	// ...

	for (;;)
	{
		std::cout << "何をしますか。[1] 一覧 [2] 検索 [3] 登録 [4] 削除 [5] 終了\n>";
		
		int action;
		std::cin >> action;
		std::cin.ignore(1000, '\n');

		if (action == 1)
		{
			// ...
		}
		else if (action == 2)
		{
			// ...
		}

		// ...
	}
}
(参考)値に std::vector を使う

std::unordered_map<int, std::vector<std::string>> のように、値に std::vector を使うことで、1 つのキーに複数の値を登録することもできます。#include <vector> が必要です。

#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main()
{
	// 英和辞典
	std::unordered_map<std::string, std::vector<std::string>> table;
	table["dog"].push_back("犬");
	table["book"].push_back("本");
	table["book"].push_back("予約する");

	for (auto&& [key, value] : table)
	{
		std::cout << key << " : ";

		for (const auto& elem : value)
		{
			std::cout << elem << ", ";
		}

		std::cout << '\n';
	}
}

グラフィックスプログラミングに必要なツールのインストールを講義でサポート

第 10 回、第 11 回は、第 12 ~ 14 回で取り組むグラフィックスプログラムのためのツールのインストールをサポートします。手順は講義資料でも説明しますが、操作が難しかったり、トラブルが生じたりすることもあるため、対面でサポートします。