LoginSignup
1
0

More than 5 years have passed since last update.

オフラインリアルタイムどう書くE28の実装例(Rust)

Last updated at Posted at 2018-11-10

オフラインリアルタイムどう書くE28の問題「増築の果ての隣室」の実装例を、Rust で。

問題 : http://nabetani.sakura.ne.jp/hena/orde28sqst/
実装リンク集 : https://qiita.com/Nabetani/items/4e3fac2746ca05a93165
イベント : https://yhpg.doorkeeper.jp/events/81346

次回のイベントは 12/8。
詳しくは
https://yhpg.doorkeeper.jp/events/82699
を御覧ください。
まだ問題も決まってないけど、 Rust での実装例を用意したいと思っている(未定) ので、Rust 好きの方も是非。。

で。
E28(増築の果ての隣室) の ruby版を( https://qiita.com/Nabetani/items/bb4b06467dbe1cc6c6dd ) Rust に移植した。

人生初の Rust。

Rust
use std::cmp;

struct Sq {
    x: i64,
    y: i64,
    w: i64,
}

impl Sq {
    fn horz(&self) -> (i64, i64) {
        (self.x, self.right())
    }
    fn vert(&self) -> (i64, i64) {
        (self.y, self.bottom())
    }
    fn right(&self) -> i64 {
        self.x + self.w
    }
    fn bottom(&self) -> i64 {
        self.y + self.w
    }
}

fn get_base(ops: &str) -> i64 {
    let mut base: i64 = 1;
    let mut ix: usize = 0;
    while ix < ops.len() {
        let op: &str = &ops[(ix + 1)..(ix + 2)];
        base *= op.parse::<i64>().expect("unexpected input");
        ix += 2;
    }
    base
}

fn add_rooms(rooms: &mut Vec<Sq>, dir: u8, size: i64) {
    let l = rooms.iter().fold(0i64, |acc, val| cmp::min(acc, val.x));
    let t = rooms.iter().fold(0i64, |acc, val| cmp::min(acc, val.y));
    let r = rooms
        .iter()
        .fold(0i64, |acc, val| cmp::max(acc, val.right()));
    let b = rooms
        .iter()
        .fold(0i64, |acc, val| cmp::max(acc, val.bottom()));
    match dir {
        b'N' => {
            let w = (r - l) / size;
            for i in 0..size {
                rooms.push(Sq {
                    x: l + i * w,
                    y: t - w,
                    w: w,
                });
            }
        }
        b'S' => {
            let w = (r - l) / size;
            for i in 0..size {
                rooms.push(Sq {
                    x: l + i * w,
                    y: b,
                    w: w,
                });
            }
        }
        b'W' => {
            let w = (b - t) / size;
            for i in 0..size {
                rooms.push(Sq {
                    x: l - w,
                    y: t + w * i,
                    w: w,
                });
            }
        }
        b'E' => {
            let w = (b - t) / size;
            for i in 0..size {
                rooms.push(Sq {
                    x: r,
                    y: t + w * i,
                    w: w,
                });
            }
        }
        _ => {
            panic!("unexpected direction");
        }
    }
}

fn has_intersection(a: (i64, i64), b: (i64, i64)) -> bool {
    if a.0 < b.0 {
        b.0 < a.1
    } else if b.0 < a.0 {
        a.0 < b.1
    } else {
        true
    }
}

fn is_nei(a: &Sq, b: &Sq) -> bool {
    if a.y == b.bottom() || a.bottom() == b.y {
        has_intersection(a.horz(), b.horz())
    } else if a.x == b.right() || a.right() == b.x {
        has_intersection(a.vert(), b.vert())
    } else {
        false
    }
}

fn solve(src: &String) -> String {
    let s: Vec<&str> = src.split("/").collect();
    let myroom_number: usize = s[1].parse::<usize>().expect("unexpected input");
    let ops = s[0];
    let base: i64 = get_base(ops);
    let mut rooms = vec![Sq {
        x: 0,
        y: 0,
        w: base,
    }];
    let mut ix = 0usize;
    while ix < ops.len() {
        let dir: u8 = ops[ix..(ix + 1)].bytes().next().unwrap();
        let op: &str = &ops[(ix + 1)..(ix + 2)];
        let size: i64 = op.parse::<i64>().expect("unexpected input");
        add_rooms(&mut rooms, dir, size);
        ix += 2
    }
    let mut neis = Vec::<String>::new();
    let myroom = &(&rooms)[myroom_number - 1];
    for ix in 0..rooms.len() {
        let room = &(&rooms)[ix];
        if is_nei(&room, &myroom) {
            neis.push(format!("{}", ix + 1));
        }
    }
    neis.join(",")
}

fn test(src: String, expected: String) {
    let actual = solve(&src);
    let okay = actual == expected;
    println!("{}, src: {}, act: {}, exp: {}", okay, src, actual, expected);
}

macro_rules! test {
    ($x:expr, $y:expr) => {
        test($x.to_string(), $y.to_string());
    };
}

fn main() {
    test!("N7N4E5/8", "1,7,12,13,14");
    test!("E1/1", "2");
    test!("N6/5", "1,4,6");
    test!("W5/3", "1,2,4");

    test!(
        "N9S9S3S6N6S7N8W4E9W7E3N5W8S9E9E9W6N3N9W8/119",
        "73,74,78,113,120,122,123,124,131,132,133,134"
    );
}

テストデータの大半は省略。

最大の不満は、main 関数内に大量の .to_string() を書かざるを得なかったところ。
たぶん書かずに済ませる方法があると思うんだけど、わからなかった。

-- 以下追記 --
マクロを使って大量の .to_string() を隠すことにした。
しかしころが好ましい方法なのかよくわからない。
-- ここまで追記 --

main 関数ほどではないけれど、

Rust
let dir: u8 = ops[ix..(ix + 1)].bytes().next().unwrap();

の部分も不満。

opsix 番目のバイトがほしい(char でもいい)だけなのに、
.bytes().next().unwrap() と3つも関数を呼んでしまっている。
たぶんここももっとシンプルに書けるんだと思うんだけど、わからなかった。

文字列を二文字ずつに分解するコードもちゃんと書けなかった。 ruby なら

ruby
"foobarbazqux".scan(/../).to_a
#=> ["fo", "ob", "ar", "ba", "zq", "ux"]

こんな感じになるが、Rust でどう書いていいのかわからず、while ループでごまかした。

あと。

cargo fmt で書式を整えたらなんか改行多めになった。

Rust
    let mut rooms = vec![Sq {
        x: 0,
        y: 0,
        w: base,
    }];

なんかは一行になってほしかった。
設定とかあるのかなぁ。

で。

Rust の感想。
なんか安心感はあるし、クロージャも書きやすくて好印象。
しかし、適当に書いても全然動かずエラーが出まくるのでちょっと怖い感じはした。

VSCode で書いたんだけど、Go と比べるとエディタのサポートが弱かった。
ユーザーの人数が少ないから仕方ないのかなぁ。

保存時に自動フォーマットしてほしいと思ったりもした。

仕事で使う可能性がなさすぎるけど、もうちょっと Rust を触ってみようと思う。

1
0
0

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
1
0