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

オフラインリアルタイムどう書く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: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:Rust
let dir: u8 = ops[ix..(ix + 1)].bytes().next().unwrap();
```

の部分も不満。

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

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

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

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

あと。

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

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

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

で。

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

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

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

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