YU2TA7KA's BLOG ~take one step at a time~

派生開発、組み込み開発周りのこと。

Rustでアラビア数字をローマ数字に変換する

はじめに

突然ですが、画像は何巻の美しいシャナかご存知でしょうか?




ローマ数字で表記されている通り19巻です。表記されている通りと言っても、実は今読んでいる、結城浩さんのプログラマの数学 第2版にてローマ数字の表記法を初めてちゃんと知りました。普段使っているアラビア数字と比べるとややこしいし、0の概念もありません!
それでせっかくなので勉強中のRustでアラビア数字をローマ数字に変換するプログラムを実装しました。

問題

  • 入力されたアラビア数字をローマ数字に変換して出力する
  • 対象となるアラビア数字の範囲は1〜1000とする
  • 対応表はWikipediaを参照する

ローマ数字 - Wikipedia

ローマ数字の表記法の特徴

・位は意味を持たず、数字そのものがその数を示す。
・ゼロがない。
・I(1)V(5)X(10)L(50)C(100)D(500)M(1000)の文字を使う。
・並べた文字が表す数を加えたものが、全体の数になる。
プログラマの数学 第二版 32pageより

アルゴリズム

1. 入力された値を対応表の降順で割り算をしていく
2. 商が1以上になったら、対応するローマ数字を結果文字列に連結する
3. 連結する文字数は商の値分繰り返す
4. 商が1以上になったときの余りで1~3を繰り返す
5. 対応表の末尾まで割り算が完了したら、そのときの結果文字列が変換結果になる
 (余りが0になった時点で変換結果は得られているため、無駄な割り算をしている。)

実装

use std::io;

struct Roman {
    roman: [(u16, String); 18],
}

impl Roman {
    /// 対応表の初期化
    /// 参照:https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%9E%E6%95%B0%E5%AD%97
    pub fn new() -> Roman {
        Roman {
            roman: [
                (1000, "M".to_string()),
                (900, "CM".to_string()),
                (500, "D".to_string()),
                (400, "CD".to_string()),
                (100, "C".to_string()),
                (90, "XC".to_string()),
                (50, "L".to_string()),
                (40, "XL".to_string()),
                (10, "X".to_string()),
                (9, "IX".to_string()),
                (8, "VIII".to_string()),
                (7, "VII".to_string()),
                (6, "VI".to_string()),
                (5, "V".to_string()),
                (4, "IV".to_string()),
                (3, "III".to_string()),
                (2, "II".to_string()),
                (1, "I".to_string()),
            ],
        }
    }

    /// アラビア数字をローマ数字へ変換する
    pub fn arabic_to_roman(&self, input: u16) -> String {
        let mut n = input;
        let mut r_string = String::new();
        for index in self.roman.iter() {
            let d = n / index.0;
            let r_ch = match d {
                0 => "".to_string(),
                _ => {
                    n = n % index.0;
                    Roman::write_roman(d, &index.1)
                }
            };
            r_string += &r_ch;
        }
        r_string
    }

    /// 該当するローマ数字の文字を追加する
    fn write_roman(d: u16, ch_roman: &String) -> String {
        let mut r = String::new();
        for _i in 0..d {
            r += &ch_roman;
        }
        r
    }
}

fn main() {
    println!("Arabic to Roman: Please input arabic number");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    let input: u16 = input.trim().parse().expect("please type a number(<=1000)");

    let r = Roman::new();
    println!("roman={:?}", r.arabic_to_roman(input));
}

github.com

もう少しやりたいこと

  • 余りが0になったら処理を終了させる実装
  • 例外値処理
  • Displayトレイトの実装

勉強になったこと

  • なんとなくRustで書けた気がする
  • テストコード(#[test])*1
  • 自動整形機能が便利(cargo fmt)

まだよくわかっていないこと

  • 所有権システム周り
  • ライフタイム周り
  • Rustっぽい書き方

おわりに

Rustとプログラミングの勉強をしたいと思い、いろいろやっているのですが、ちょうど良い題材を見つけるのが結構難しいなぁと思う今日このごろです。そんな中、今回の変換プログラムは良かったです。そして、私はプログラミング能力がほぼ無いと気づきましたので、精進します。


*1:長いので興味ある方はgithubを見てください