ソートしてトップ3を表示するプログラム
あるテストにおける生徒5人のスコアを小さい順に並べ、ついでにスコアの高い順にトップ3まで出力し、表示するプログラムです。生徒の受けたテストの項目はN個 (入力時に設定可能) あり、それぞれを足し合わせて合計した数字を生徒同士で比べ合います。
int縛り
難しいのに出遭ったら、慌てずに仕組みを図でイメージ化して声で頭に叩き込もうと思います。
上の画像は、この問題を考えるのに声出しに合わせて描いた図です。4、5の部分を編集して見やすくしています。ここまでしないと眺めるだけでは絶対に分かりませんでした。
説明文は日本語的にややこしいし、一回読んだだけでは題意を汲みとれず、何度も最初に戻ってイチから考え直す羽目になり、そのせいで多くの時間を浪費してしまいました。
でも、八つ当たり気味に声を出して鉛筆を動かすと、意外にスンナリと内容が頭に入ってきて(笑)。
想像しながら考えるのは難しくても、図を描きながら声に出して、誰かに教えるように呟いていると「この4個にアレをかけていって、その合計を出す!それが生徒一人の合計スコアね!それを5人分、つまり5回繰り返せば良いのね!」と頭を整理出来て、すぐに二重ループに持ち込めました。
動作の全体像が大体イメージ出来たところで、実装作業に入りました。作るのにかかった時間は70分でした。(※50行程度のコードの作成に1時間前後を要するのはエンジニアとしてどうか等、現場からのリアルな評価を頂きたいです)
#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:項目別・クリア1回毎のスコア, 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){ //連続的な入れ替わりが起きる可能性を考慮し、全ての配列要素に対して処理Aが行われるようにする
int f = 0;
while(f < num_of_std - 1){ //処理A:'\0'とその手前の最後部の要素を除く配列要素を1文字ずつ調べ、条件式に応じて適宜入れ替える
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;
}
}
僕がコンパイル・実行してみた感じでは、上のプログラムは特にエラーなく正常に動きました。
C++の標準入力には、Cにはない魅力があります。それは、std::cinがスペースを入力の区切りとみなす他、改行文字についても同様に区切りであると認識してくれる点です。
つまり、スペースも改行も同じ、入力の区切りになるのです。この仕組みは、標準入力からn個 (可変) の数値や文字列を読み込ませたい時に役立ちます。
参考までに、僕の今回の入力例を以下に示しておきます。1行目に項目数・生徒数・表示するランクの数を、2行目に項目それぞれのクリア1回毎のスコアを、3行目以降にクリア数を並べています (もちろん、イチイチ行で分けずにスペース区切りで1列に並べてしまうのもアリです) 。
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型の整数だったので、結果もきれいにまとまりました。
小数混じりの四捨五入タイプ
では、一部の入力値を小数表記にしたらどうなるのか。
足し引き掛け割りは同じ型同士で行う事に注意して、前述のプログラムに変更を加えていきます。
標準入力する内容は以下の通りです。
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
四捨五入する関数は、自作すると煩雑になるので、素直にツールに頼ります。
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;
}
次のサイトのコードを参考にしています。
int型の変数のうち、適切なものをdouble型に変えていきます。double型の初期化の際は例え「0」であっても「0.0」のように小数表記にする事、そしてfor文の変数はint型から変えない事に気をつけます。
#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)が正しく行われたのか、自分でも疑問に思っています。もしわかる方いましたら、ぜひご教授ください。
以上、四捨五入を使ったランク付けの問題でした。
まとめ
最後に一言。学習中の方には特に、シチュエーションを考える際に声出しをして図を描くというのが非常に有効な方法です。ぜひ実践してみてください。