オフラインリアルタイムどう書く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。
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
関数ほどではないけれど、
let dir: u8 = ops[ix..(ix + 1)].bytes().next().unwrap();
の部分も不満。
ops
の ix
番目のバイトがほしい(char でもいい)だけなのに、
.bytes().next().unwrap()
と3つも関数を呼んでしまっている。
たぶんここももっとシンプルに書けるんだと思うんだけど、わからなかった。
文字列を二文字ずつに分解するコードもちゃんと書けなかった。 ruby なら
"foobarbazqux".scan(/../).to_a
#=> ["fo", "ob", "ar", "ba", "zq", "ux"]
こんな感じになるが、Rust でどう書いていいのかわからず、while
ループでごまかした。
あと。
cargo fmt
で書式を整えたらなんか改行多めになった。
let mut rooms = vec![Sq {
x: 0,
y: 0,
w: base,
}];
なんかは一行になってほしかった。
設定とかあるのかなぁ。
で。
Rust の感想。
なんか安心感はあるし、クロージャも書きやすくて好印象。
しかし、適当に書いても全然動かずエラーが出まくるのでちょっと怖い感じはした。
VSCode で書いたんだけど、Go と比べるとエディタのサポートが弱かった。
ユーザーの人数が少ないから仕方ないのかなぁ。
保存時に自動フォーマットしてほしいと思ったりもした。
仕事で使う可能性がなさすぎるけど、もうちょっと Rust を触ってみようと思う。