Edited at

ことわざプログラミング: 1. "時は金なり"

More than 3 years have passed since last update.


はじめに

"ことわざをプログラミング"することで Rust を勉強していきます.


本日のお題: 時は金なり


西洋のことわざ「Time is money.」から。

時間は貴重なものであって、金銭と同じように大切で価値があるのだから、浪費するものではないという戒め。

時間は無駄に費やすものではなく、有効に使うべきである。

http://kotowaza-allguide.com/to/tokiwakanenari.html



本日の学び


  • 標準入力 std::io

  • 変数のキャスト

  • 2つの文字列型: &strString

  • 時間に関するモジュール std::time の使い方

  • 標準出力 println!マクロ


GitHub レポジトリ

https://github.com/skyshk/time_is_money


仕様


  1. 標準入力から時給を文字列入力

  2. 時給文字列から"秒給"を算出

  3. 一秒ごとに,「無駄に過ごした時間で稼げるはずだったお金」を標準出力


コード全体


time_is_money

// Proverb Programming: "Time is money"

// Count duration time, convert it to money, and display.

use std::time;
use std::io;

fn main() {
println!("Please input your hourly wage:");
let mut hourly_wage_string = String::new();
io::stdin().read_line(&mut hourly_wage_string)
.expect("Failed to read line");
hourly_wage_string = hourly_wage_string.trim_right().to_string();
println!("Your hourly wage is :{}",hourly_wage_string);
let hourly_wage: f32 = hourly_wage_string.parse().unwrap();
let second_wage: f32 = hourly_wage/60.0/60.0;

// Count time in second
let start_time = time::Instant::now();
let mut count_second:u64 = 0;
loop {
if count_second <= start_time.elapsed().as_secs() {
count_second += 1;
println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) );
}
}
}



実行結果

$./time_is_money

Please input your hourly wage:
907 # 時給を入力すると...
Your hourly wage is :907
# 起動から現在までに消費した時間 (duration) と,
# そこで得られるはずだったお金が一秒ごとに可視化されます
You have wasted 1 secs. = 0.252 Yen
You have wasted 2 secs. = 0.504 Yen
You have wasted 3 secs. = 0.756 Yen
You have wasted 4 secs. = 1.008 Yen
You have wasted 5 secs. = 1.260 Yen
You have wasted 6 secs. = 1.512 Yen
You have wasted 7 secs. = 1.764 Yen
You have wasted 8 secs. = 2.016 Yen
You have wasted 9 secs. = 2.268 Yen
You have wasted 10 secs. = 2.519 Yen
You have wasted 11 secs. = 2.771 Yen

...


コードの解説


1. 標準入力から時給を入力

まずは,時とお金を変換するための「時給」を入力させます.

Rust では標準入力の受け付けは以下のようにするらしいです.

let mut hourly_wage_string = String::new();

io::stdin().read_line(&mut hourly_wage_string)
.expect("Failed to read line");

参考: https://doc.rust-lang.org/book/guessing-game.html#processing-a-guess

io::stdin().read_line() が本丸の標準入力処理を担います.

read_line() の引数には,標準入力を受け取る String 型のオブジェクトが必要です.

ですので,予め次のように文字列を受け取るための String インスタンスを生成しておきます.

let mut hourly_wage_string = String::new();

これは mutable でなくてはいけません.

こうして生成した空のhourly_wage_string文字列インスタンスを,

io::stdin().read_line(&mut hourly_wage_string)

のように引数に渡します.

&mutは「hourly_wage_stringの所有権を関数に借用させます,しかもその内容は変更可です」といった意味らしいです

(参考: https://doc.rust-lang.org/book/references-and-borrowing.html#mut-references).

※ 所有権・借用の概念はRustの根幹的概念ですが,まだまだ理解しきれていないところがあるので,後々勉強したいですね.

その次の行の

.expect("Failed to read line");

は例外処理で,read_line 関数が正しく処理されなかった時に引数のメッセージを出力して落ちるようになっています.


2. 時給文字列から"秒給"を算出

さて,受け取った時給は扱いにくいので,秒給に変換してやりたいです.

ただしhourly_wage_stringString型なので,これを計算可能な数値型にしてやる必要があります.

hourly_wage_string = hourly_wage_string.trim_right().to_string();

let hourly_wage: f32 = hourly_wage_string.parse().unwrap();
let second_wage: f32 = hourly_wage/60.0/60.0;

一番最後の行は単純に時給hourly_wageを秒給second_wageに変換しているだけです.

その手前二行で,入力された文字列hourly_wage_stringを浮動小数点hourly_wageに型変換(キャスト)しています.

ここで一つ落とし穴が.

最初は標準入力から渡ってきたhourly_wage_stringを直接なんとかキャストしようと試みましたが,どうしてもエラーになりうまくいきませんでした.

ここでよくよく調べてみると,標準入力から渡ってきた

hourly_wage_stringには,数値の文字列だけでなく末尾に改行文字列\nが含まれており,

これが型変換の邪魔をしていました.

なので型変換をする前に,Stringクラスのtrim_rightメソッドで末尾の邪魔な文字列(空白,\nなど)を削除しています:

hourly_wage_string = hourly_wage_string.trim_right().to_string();

これはPythonで言うところのstr.stripメソッドですね.

trim_rightメソッドの返り値はString型とは似て非なる&str型なので,

これをto_stringメソッドで再度String型に戻してやります.

※ どうしてRustには2つの文字列型があるのか,まだご利益は感じられていないのが現状です...

こうして末尾の\nが削除されて数値文字列だけになったはずのhourly_wage_string文字列.これをString型のparseメソッドを使って浮動小数点であるf32型にキャストします.

let hourly_wage: f32 = hourly_wage_string.parse().unwrap();

parseメソッドの返り値は,キャストされた数値そのものではなく,Result型と呼ばれる返り値用の特殊な型です.欲しい返り値本体はこのResult型の内部に包まれている(wrap)ので,解いてやる(unwrap)してやることで得ることができます.


3. 一秒ごとに,「無駄に過ごした時間で稼げるはずだったお金」を標準出力

秒給を得られたら,一秒ごとに,そこまでの経過時間と,それを秒給で換算した金額を永遠に出力します.

ここで肝となるのは,時間を司るstd::timeモジュールを使いこなすことです.

use std::time;

let start_time = time::Instant::now();
let mut count_second:u64 = 0;
loop {
if count_second <= start_time.elapsed().as_secs() {
count_second += 1;
println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) );
}
}

まず,最初の時間を取得します.

let start_time = time::Instant::now();

start_timetime::Instant型と呼ばれる構造体のインスタンスです.

直感的には,あるシステム時間のスナップショット的なオブジェクトです.

time::Instant型の構造体にはelapsedメソッドというものが実装されており,

これを呼び出すとtime::Instantが作成された時間からの経過時間(duration)が返ってきます.

let duration: Duration = start_time.elapsed()

経過時間はDuration型と呼ばれる時間を表す構造体で表現されており,as_secsメソッドを呼び出すことで経過時間が秒で数値として得られます.

let duration_sec: u64 = duration.as_secs()

この処理を無限ループloop{...}の内部で常に回しておき,

一秒経過するごとに経過時間と累積金額を出力しています.

println!("You have wasted {:.0} secs. = {:.3} Yen",count_second,second_wage* (count_second as f32) )

標準出力を担うprintln!マクロへは,第一引数にフォーマットを,第二引数以降にフォーマットの中の{}に当たる箇所に代入したい変数を渡します.

{}の内部に{:.3}などと書くと,小数点下三桁までにそろえて出力してくれたりします.


最後に

「この書き方は普通じゃない」「こんなエレガントな書き方があるよ」というアドバイスがありましたら,ぜひコメントや編集リクエストをお願いします.

次のことわざ: 「蛙の子は蛙」でクラスの作り方を勉強