Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

LINE のコーディングテストを Rust で解いてみた

Rust の練習としてLINEのコーディングをやってみました。以前からずっとやりたかったのですが、問題も長くて面倒でやってなかったんですよね(実際めんどうだった)…

今回解いた問題

以下解説していきます

(注: 入出力の例が一切なく、ジャッジシステムも見当たらなかったので、コードが問題の意図通り動いているか保証されていません。仕様の誤読やコーディングミスがある恐れがあります)

問題概要

問題本文はこちら

タクシーの走行記録が入力として渡されるので料金を求めよ。

  • 走行距離に応じた料金と、低速走行時間に応じた料金の合計が料金
    • 走行距離は初乗り410円、1052m以降は237mごとに80円
    • ただし、深夜時間帯は走った距離が1.25倍されて計算される
    • 低速走行とは、10km/h以下で走ることを指す。降車までの総低速時間に対して90秒ごとに80円かかる。

ざっと要約するとこんな感じです。

方針

仕様があまりに複雑なので、テスト駆動っぽく解きました。

  1. 走行距離のみ考慮して計算する(深夜時間帯、低速走行を含まないケースのテストを書く)
  2. 深夜時間帯を考慮して計算する
  3. 低速走行を考慮して計算する

だいたいこんな感じで段階を踏みながら関数をリファクタリングしていきました。

実際のコード

テストコードを抜いておよそ80行になりました。

use std::io::{self, BufRead, BufReader};

type Record = Vec<(f64, f64)>;
fn to_sec(h: u32, m: u32, s: f64) -> f64 {
    ((h * 60 + m) * 60) as f64 + s
}
fn is_midnight(s: f64) -> bool {
    // 22時になってから5時になるまで(5時を含まない)が深夜時間帯
    match s {
        t if t < to_sec(5, 0, 0.0) => true,
        t if t >= to_sec(22, 0, 0.0) => true,
        _ => false,
    }
}
// 走行距離を求める関数。深夜時間帯は実際に走った距離の1.25倍走行したと見なす
fn measure_distance(v: &Record) -> f64 {
    let mut distance = v.into_iter().fold(0.0, |sum, (_, meter)| sum + meter);
    for x in v.windows(2) {
        let (t1, ..) = x[0];
        let (t2, meter2) = x[1];
        if is_midnight(t1) && is_midnight(t2) {
            distance = distance + 0.25 * meter2;
        }
    }
    distance
}
// 低速(10km/h以下)で走った総時間を求める関数。深夜時間帯は実際の低速走行時間の1.25倍走行したと見なす。
fn sum_slow_running_time(v: &Record) -> f64 {
    let mut slow_running_time = 0.0;
    for x in v.windows(2) {
        let (t1, ..) = x[0];
        let (t2, meter2) = x[1];
        let dt = t2 - t1;
        if (meter2 / dt) * 60.0 * 60.0 > 10000.0 {
            continue;
        }

        slow_running_time += if is_midnight(t1) && is_midnight(t2) {
            dt * 1.25
        } else {
            dt
        }
    }
    slow_running_time
}
// 走行距離に応じた料金
fn calc_distance_based_fare(v: &Record) -> u32 {
    410 + ((measure_distance(v) - 1052.0) / 237.0).ceil() as u32 * 80
}
// 低速で走った時間に対する料金
fn calc_slow_fare(v: &Record) -> u32 {
    (sum_slow_running_time(v) / 90.0).floor() as u32 * 80
}
fn calc_fare(v: &Record) -> u32 {
    calc_distance_based_fare(v) + calc_slow_fare(v)
}

fn read_from_stdin() -> Record {
    let mut time_records: Record = vec![];
    let mut reader = BufReader::new(io::stdin());
    let mut s = String::new();

    while reader.read_line(&mut s).expect("Failed to read line.") > 0 {
        let v: Vec<_> = s.split_whitespace().collect();

        let meter = v[1].parse().unwrap_or(0.0);
        let time: Vec<_> = v[0].split(':').collect();

        let hour = time[0].parse().unwrap_or(0);
        let min = time[1].parse().unwrap_or(0);
        let sec = time[2].parse().unwrap_or(0.0);
        time_records.push((to_sec(hour, min, sec), meter));
        s.clear();
    }
    time_records
}
fn main() {
    let time_records = read_from_stdin();
    println!("{}", calc_fare(&time_records));
}

テストコードはこんな感じです。

#[test]
fn distance_based_fare() {
    // 通常時間帯のみ、低速賃金や深夜割増が発生しないケース
    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(7, 1, 0.0), 1052.0)];
    assert_eq!(measure_distance(&record), 1052.0);
    assert_eq!(calc_distance_based_fare(&record), 410); // 1052m/min = 63.12km/h

    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(7, 1, 0.0), 1053.0)];
    assert_eq!(measure_distance(&record), 1053.0);
    assert_eq!(calc_distance_based_fare(&record), 490);

    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(7, 1, 0.0), 1289.0)];
    assert_eq!(measure_distance(&record), 1289.0);
    assert_eq!(calc_distance_based_fare(&record), 490);

    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(7, 1, 0.0), 1290.0)];
    assert_eq!(measure_distance(&record), 1290.0);
    assert_eq!(calc_distance_based_fare(&record), 570);
}
#[test]
fn midnight_fare() {
    // 深夜割増のケース
    let record = vec![(to_sec(0, 0, 0.0), 0.0), (to_sec(0, 1, 0.0), 841.0)];
    assert_eq!(measure_distance(&record), 1051.25);
    assert_eq!(calc_distance_based_fare(&record), 410);

    let record = vec![(to_sec(0, 0, 0.0), 0.0), (to_sec(0, 1, 0.0), 842.0)];
    assert_eq!(measure_distance(&record), 1052.5);
    assert_eq!(calc_distance_based_fare(&record), 490);

    let record = vec![(to_sec(0, 0, 0.0), 0.0), (to_sec(0, 1, 0.0), 1031.0)];
    assert_eq!(measure_distance(&record), 1288.75);
    assert_eq!(calc_distance_based_fare(&record), 490);

    let record = vec![(to_sec(0, 0, 0.0), 0.0), (to_sec(0, 1, 0.0), 1031.3)];
    assert_eq!(measure_distance(&record), 1289.125);
    assert_eq!(calc_distance_based_fare(&record), 570);
}
#[test]
fn midnight_fare2() {
    // 通常時間帯 → 深夜時間帯のケース
    let record = vec![
        (to_sec(21, 59, 0.0), 0.0),
        (to_sec(22, 0, 0.0), 927.0),
        (to_sec(22, 0, 10.0), 100.0),
    ];
    assert_eq!(measure_distance(&record), 1052.0);
    assert_eq!(calc_distance_based_fare(&record), 410);

    let record = vec![
        (to_sec(21, 59, 0.0), 0.0),
        (to_sec(22, 0, 0.0), 927.1),
        (to_sec(22, 0, 10.0), 100.0),
    ];
    assert_eq!(measure_distance(&record), 1052.1);
    assert_eq!(calc_distance_based_fare(&record), 490);

    let record = vec![
        (to_sec(21, 59, 0.0), 0.0),
        (to_sec(22, 0, 0.0), 927.0),
        (to_sec(22, 0, 10.0), 100.1),
    ];
    assert_eq!(measure_distance(&record), 1052.125);
    assert_eq!(calc_distance_based_fare(&record), 490);
}
#[test]
fn slow_fare() {
    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(8, 0, 0.0), 10000.1)];
    assert_eq!(sum_slow_running_time(&record), 0.0);

    let record = vec![(to_sec(7, 0, 0.0), 0.0), (to_sec(8, 0, 0.0), 10000.0)];
    assert_eq!(sum_slow_running_time(&record), 3600.0);

    let record = vec![(to_sec(0, 0, 0.0), 0.0), (to_sec(1, 0, 0.0), 10000.0)];
    assert_eq!(sum_slow_running_time(&record), 3600.0 * 1.25);
}