LoginSignup
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-04-26

はじめに

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

本日のお題: 時は金なり

西洋のことわざ「Time is money.」から。
時間は貴重なものであって、金銭と同じように大切で価値があるのだから、浪費するものではないという戒め。
時間は無駄に費やすものではなく、有効に使うべきである。
http://kotowaza-allguide.com/to/tokiwakanenari.html

本日の学び

  • 標準入力 std::io
  • 変数のキャスト
  • 2つの文字列型: &strString
  • 時間に関するモジュール std::time の使い方
  • 標準出力 println!マクロ

GitHub レポジトリ

仕様

  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}などと書くと,小数点下三桁までにそろえて出力してくれたりします.

最後に

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3