なんの記事?
Rustの学習ドキュメントである「The Rust Programming Language」の問題を初心者が解いてみたという記事です。
筆者はエンジニアでもなんでもないただ趣味でRustを学習してみただけの初心者ですので温かい目で見守ってください。ほぼ備忘録のような記事ですが「ここのコードが良くない。もっとこうした方がいい。」といった指摘コメントをいただけると嬉しいです。
今回解いてみた問題
「The Rust Programming Language」の8.3に書かれている問題を解きました。
内容は以下の通りです。
整数のリストが与えられ、ベクタを使ってmean(平均値)、median(ソートされた時に真ん中に来る値)、 mode(最も頻繁に出現する値; ハッシュマップがここでは有効活用できるでしょう)を返してください。
解答
main関数
まずユーザーには実行したい操作を選んでもらう。操作は「0.整数の入力」「1.平均値の表示」「2.中央値の表示」「3.最頻値の表示」「4.入力した整数のリセット」「5.プログラム終了」の6つを用意。
ユーザーが入力した数字に応じて必要な関数を動かして平均値などを導出するイメージです。
fn main() {
let mut numbers: Vec<i32> = Vec::new(); //数値を保存しておくベクタを用意
loop {
println!("\n\n入力された整数の一覧は以下の通りです");
println!("{:?}\n", numbers);
println!("行いたい操作の番号を選んでください");
println!("0:整数を入力する\n1:平均値を表示\n2:中央値を表示\n3:最頻値を表示\n4:リセット\n5:終了");
//ユーザーに操作を選んでもらう
let mut operation_num = String::new();
io::stdin()
.read_line(&mut operation_num)
.expect("入力に失敗しました");
let operation_num: u8 = match operation_num.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("無効な値です。再度選択してください。");
continue;
}
};
//選んでもらった数字によって必要な関数を動かす
match operation_num {
0 => input_number(&mut numbers),
1 => print_average(&numbers),
2 => print_median(&numbers),
3 => print_mode(&numbers),
4 => numbers = Vec::new(),
5 => break,
_ => println!("無効な値です。再度選択してください。"),
}
}
}
ユーザーが不正な値を入力した場合に再度入力を求めるようにしました。
matchを2回使っているのが気になるところ。絶対1つにまとめられそうな気がしているので要改善です。
input_number関数
main関数でユーザーが「0」を入力した場合に動く関数です。
ユーザーに整数を入力してもらい、その値をnumbersというベクタに格納していくようにしました。
fn input_number(numbers: &mut Vec<i32>) { //ベクタの可変参照を受け取る
loop {
println!("整数を入力してください");
println!("qで入力を終了します");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("入力に失敗しました");
let input = input.trim(); //入力値の改行を取り除く
if &input[..] != "q" { //$str型で比較
// 入力された整数値(String)をi32に型変換
let input: i32 = match input.parse() {
Ok(num) => num,
Err(_) => {
println!("{}は整数ではありません。再度入力してください。", input);
continue;
}
};
numbers.push(input);
} else {
//qが入力された場合にループから抜け出す
break;
};
}
}
if &input[..] != "q"
の比較がなかなか書けずに苦戦しました。入力値はString型かつ改行コードを含んでいるとのことなので、trim()
で改行を取り除き、文字列スライスを利用して&str型に変換する方法をとりました。
print_average関数
main関数でユーザーが「1」を入力した場合に動く関数です。
ベクタに格納されている整数の合計を求め、それを要素数で割ることで平均値を出しました。
fn print_average(numbers: &Vec<i32>) {
let mut sum = 0;
let len = numbers.len();
for num in numbers {
sum += num;
}
println!("平均値は{}です。", sum as f64 / len as f64);
}
as
を使用した型キャストを初めて使用しました。浮動小数点型に変換して計算をしないと、平均値の小数点以下が切り捨てられてしまうんですね。
C言語だと「浮動小数点型 / 整数型」は浮動小数点型で答えを出してくれたと思うのですが、Rustだと型を揃えておかないとコンパイルに失敗するみたいです。
print_median関数
main関数でユーザーが「2」を入力した場合に動く関数です。
sort()
メソッドで数字を昇順に並び替えて、中央値を出しました。要素数が奇数個か偶数個かで計算方法を変える必要があったのでmatch
で処理を分けましたが、流石にif
を使った方が良かったかもしれません。
fn print_median(numbers: &Vec<i32>) {
let mut sort_numbers = numbers.clone(); //ソート用にベクタのクローンを作成
sort_numbers.sort();
let len = sort_numbers.len();
let target = len / 2;
let median: f64 = match len % 2 {
0 => (&sort_numbers[target] + &sort_numbers[target - 1]) as f64 / 2.0,
1 => (*&sort_numbers[target]) as f64,
_ => {
println!("エラー。もう一度やり直してください。");
0 as f64
}
};
println!("中央値は{}です。", median);
}
print_mode関数
main関数でユーザーが「3」を入力した場合に動く関数です。
{整数値: 登場回数}という形式のハッシュマップを作成して、登場回数の最も多い整数値を最頻値として出力しました。
fn print_mode(numbers: &Vec<i32>) {
let mut count_num_map = HashMap::new();
let mut mode = Vec::new();
//{整数値: 登場回数}という形式のハッシュマップを作成
for num in numbers {
let count = count_num_map.entry(num).or_insert(0);
*count += 1;
}
let mut compare_num = 0;
for (num, count) in &count_num_map {
if count > &compare_num { //登場回数を比較。登場回数が単独トップであれば、ベクタをリセットして新たな整数値を格納。
mode = Vec::new();
mode.push(num);
compare_num = *count;
} else if count == &compare_num { //登場回数が同じであれば、ベクタに整数値を追加。
mode.push(num);
}
}
mode.sort();
println!("最頻値は{:?}です", mode);
}
2つ目のfor文で入力された各整数値の登場回数を比較しています。登場回数が最多であれば、その整数値をベクタに格納していき、最後に出力をしています。単独で最多の場合と最多タイの場合で条件分岐をしていますが、もっと簡潔な方法はないものでしょうか。。。
終わりに
ただただコードを書き終わった直後の気持ちを書き殴っただけの記事になりました。もし最後まで見てくださった方がいらっしゃればありがとうございます。
コードを書くことで、ドキュメントを読むだけでは得られない気づきがたくさんあることが分かりましたので、引き続きその気づきを備忘録として書き殴っていきたいと思います。