【C++】標準入力→計算・四捨五入→ランクをつけて上位◯個まで公開

# ソートしてトップ３を表示するプログラム
**あるテストにおける生徒５人のスコアを小さい順に並べ、ついでにスコアの高い順にトップ３まで出力し、表示するプログラム**です。生徒の受けたテストの項目はＮ個 (入力時に設定可能) あり、それぞれを足し合わせて合計した数字を生徒同士で比べ合います。

## int縛り
難しいのに出遭ったら、慌てずに仕組みを図でイメージ化して声で頭に叩き込もうと思います。
![PowerPoint スライド ショー  -  プレゼンテーション1 2022_10_29 23_25_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2610341/96da0e6b-b0df-c4d2-d9d9-88de6abb2221.png)
上の画像は、この問題を考えるのに声出しに合わせて描いた図です。４、５の部分を編集して見やすくしています。ここまでしないと眺めるだけでは絶対に分かりませんでした。
説明文は日本語的にややこしいし、一回読んだだけでは題意を汲みとれず、何度も最初に戻ってイチから考え直す羽目になり、そのせいで多くの時間を浪費してしまいました。
でも、八つ当たり気味に声を出して鉛筆を動かすと、意外にスンナリと内容が頭に入ってきて(笑)。

想像しながら考えるのは難しくても、図を描きながら声に出して、誰かに教えるように呟いていると「この４個にアレをかけていって、その合計を出す！それが生徒一人の合計スコアね！それを５人分、つまり５回繰り返せば良いのね！」と頭を整理出来て、すぐに二重ループに持ち込めました。

動作の全体像が大体イメージ出来たところで、実装作業に入りました。作るのにかかった時間は７０分でした。（※５０行程度のコードの作成に１時間前後を要するのはエンジニアとしてどうか等、現場からのリアルな評価を頂きたいです）

```c++:作成したプログラム
#include <iostream>
using namespace std;

int main(){
	
	int num_of_item, num_of_std, num_of_rank;   //num_of_item：項目数, num_of_std：生徒数, num_of_rank：表示するランクの数
	cin >> num_of_item;
	cin >> num_of_std;
	cin >> num_of_rank;
	
	int score_per_one[10], num_of_CLEAR[1000];   //score_per_one：項目別・クリア１回毎のスコア, num_of_CLEAR：項目別・生徒別・生徒がその項目でクリアした数
	
	int a = 0;
	while(a < num_of_item){
		cin >> score_per_one[a];
		a++;
	}
	
	int b = 0;
	while(b < num_of_item * num_of_std){
		cin >> num_of_CLEAR[b];
		b++;
	}
	
	int c = 0, sum[10];
	while(c < num_of_std){
		sum[c] = 0;
		for(int d = 0; d < num_of_item; d++){
			int pt = 0;
			pt = score_per_one[d] * num_of_CLEAR[4*c+d%4];
			sum[c] += pt;
		}
		c++;
	}
	
	int e = 0;
	while(e < num_of_std){   //連続的な入れ替わりが起きる可能性を考慮し、全ての配列要素に対して処理Ａが行われるようにする
		int f = 0;
		while(f < num_of_std - 1){   //処理Ａ：'\0'とその手前の最後部の要素を除く配列要素を１文字ずつ調べ、条件式に応じて適宜入れ替える
			if(sum[f] > sum[f+1]){
				int value;
				value = sum[f];
				sum[f] = sum[f+1];
				sum[f+1] = value;
			}
			f++;
		}
		e++;
	}
	
	cout << "小さい順にソート→ ";
	for(int i = 0; i < num_of_std; i++){
		cout << sum[i] << " ";
	}
	cout << endl;
	
	for(int g = 0; g < num_of_rank; g++){
		cout << "第" << g+1 << "位の生徒の合計スコア：" << sum[num_of_std - 1 - g] << endl;
	}
	
}
```

僕がコンパイル・実行してみた感じでは、上のプログラムは特にエラーなく正常に動きました。

https://programming-place.net/ppp/contents/cpp2/main/input_string.html#multi_input:~:text=std%3A%3Acin%20%3E%3E%20%E3%81%AF%E3%80%81%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%82%92%E5%85%A5%E5%8A%9B%E3%81%AE%E5%8C%BA%E5%88%87%E3%82%8A%E3%81%A8%E3%81%BF%E3%81%AA%E3%81%99%E3%81%A8%E3%81%84%E3%81%86%E3%81%93%E3%81%A8%E3%81%A7%E3%81%97%E3%81%9F%E3%81%8C%E3%80%81%E6%94%B9%E8%A1%8C%E6%96%87%E5%AD%97%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%82%82%E5%90%8C%E6%A7%98%E3%81%AB%E5%8C%BA%E5%88%87%E3%82%8A%E3%81%A7%E3%81%82%E3%82%8B%E3%81%A8%E8%AA%8D%E8%AD%98%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

C++の標準入力には、Cにはない魅力があります。それは、**std::cinがスペースを入力の区切りとみなす他、改行文字についても同様に区切りであると認識してくれる**点です。
つまり、スペースも改行も同じ、入力の区切りになるのです。この仕組みは、標準入力からn個 (可変) の数値や文字列を読み込ませたい時に役立ちます。
参考までに、僕の今回の入力例を以下に示しておきます。１行目に項目数・生徒数・表示するランクの数を、２行目に項目それぞれのクリア１回毎のスコアを、３行目以降にクリア数を並べています (もちろん、イチイチ行で分けずにスペース区切りで１列に並べてしまうのもアリです) 。

:::note info
4 5 3
10 20 30 40
9 10 11 12
5 6 7 8
13 14 15 16
1 2 3 4
17 18 19 20
:::

今回は入力する数値全てがint型の整数だったので、結果もきれいにまとまりました。

## 小数混じりの四捨五入タイプ
では、一部の入力値を小数表記にしたらどうなるのか。
足し引き掛け割りは同じ型同士で行う事に注意して、前述のプログラムに変更を加えていきます。
標準入力する内容は以下の通りです。

:::note info
4 5 3
0.1 1.9 3.3 5.8
9 10 11 12
5 6 7 8
13 14 15 16
1 2 3 5
17 18 19 20
:::

四捨五入する関数は、[自作すると煩雑になる](https://qiita.com/weemiee/items/161cdfbfa72101c26045)ので、素直にツールに頼ります。

```c++
double round_n(double number, double n){   //「小数第n位を四捨五入する」仕組み
	number *= pow(10, n-1);   //➀10の(n-1)乗倍（四捨五入する位置を小数点に合わせる）
	number = round(number);
	number /= pow(10, n-1);   //➁10の(n-1)乗分の1（➀から元に戻す）
	return number;
}
```

次のサイトのコードを参考にしています。

https://moritomo7315.hatenablog.com/entry/2019/02/09/140757


int型の変数のうち、適切なものをdouble型に変えていきます。double型の初期化の際は例え「0」であっても「0.0」のように小数表記にする事、そしてfor文の変数はint型から変えない事に気をつけます。

```c++:完成プログラム
#include <iostream>
#include <cmath>
using namespace std;

int main(){
	
	int num_of_item, num_of_std, num_of_rank;
	cin >> num_of_item;
	cin >> num_of_std;
	cin >> num_of_rank;
	
	double score_per_one[10], num_of_CLEAR[1000];
	
	int a = 0;
	while(a < num_of_item){
		cin >> score_per_one[a];
		a++;
	}
	
	int b = 0;
	while(b < num_of_item * num_of_std){
		cin >> num_of_CLEAR[b];
		b++;
	}
	
	int c = 0;
	double sum[10];
	while(c < num_of_std){
		sum[c] = 0.0;
		for(int d = 0; d < num_of_item; d++){
			double pt = 0.0;
			pt = score_per_one[d] * num_of_CLEAR[4*c+d%4];
			sum[c] += pt;
		}
		cout << "生徒" << c+1 << "の四捨五入前の合計スコア：" << sum[c] << endl;
		sum[c] *= pow(10, 0);   //四捨五入部分
		sum[c] = round(sum[c]);   //四捨五入部分
		sum[c] /= pow(10, 0);   //四捨五入部分
		c++;
	}
	
	int e = 0;
	while(e < num_of_std){
		int f = 0;
		while(f < num_of_std - 1){
			if(sum[f] > sum[f+1]){
				double value;
				value = sum[f];
				sum[f] = sum[f+1];
				sum[f+1] = value;
			}
			f++;
		}
		e++;
	}
	
	cout << "（四捨五入の後）小さい順にソート→ ";
	for(int i = 0; i < num_of_std; i++){
		cout << sum[i] << " ";
	}
	cout << endl;
	
	for(int g = 0; g < num_of_rank; g++){
		cout << "第" << g+1 << "位の生徒の合計スコア：" << sum[num_of_std - 1 - g] << endl;
	}
	
}
```

これに対する出力表示は次の通りとなりました。

```
生徒1の四捨五入前の合計スコア：125.8
生徒2の四捨五入前の合計スコア：81.4
生徒3の四捨五入前の合計スコア：170.2
生徒4の四捨五入前の合計スコア：42.8
生徒5の四捨五入前の合計スコア：214.6
（四捨五入の後）小さい順にソート→ 43 81 126 170 215 
第1位の生徒の合計スコア：215
第2位の生徒の合計スコア：170
第3位の生徒の合計スコア：126
```

実行結果にて、生徒別合計スコアの最小値は42.8となり、これは
0.1 × 1 + 1.9 × 2 + 3.3 × 3 + 5.8 × 5 = 42.8
に一致します。同じく生徒別合計スコアの最大値は214.6となり、これは
0.1 × 17 + 1.9 × 18 + 3.3 × 19 + 5.8 × 20 = 214.6
に一致します。
実行する上でのコンパイルエラーもないため、プログラムは誤った動作をしていないと判断しました。

しかしここで、入力内容からも分かるように、僕はnum_of_CLEARにあたる数値を全て整数として入力し、コンピュータに認識させていました。にもかかわらず何故double型の計算（pt）が正しく行われたのか、自分でも疑問に思っています。もしわかる方いましたら、ぜひご教授ください。

以上、四捨五入を使ったランク付けの問題でした。

# まとめ
最後に一言。学習中の方には特に、**シチュエーションを考える際に声出しをして図を描く**というのが非常に有効な方法です。ぜひ実践してみてください。