オフラインリアルタイムどう書くE27の問題「とあるカードゲーム」の実装例を、Rust で。
問題 : http://nabetani.sakura.ne.jp/hena/orde27cardgame/
実装リンク集 : https://qiita.com/Nabetani/items/cdc102d186faaf542574
イベント : https://yhpg.doorkeeper.jp/events/79705
次回のイベントは 12/8。
詳しくは
https://yhpg.doorkeeper.jp/events/82699
を御覧ください。
まだ問題も決まってないけど、 Rust での実装例を用意したいと思っている(未定) ので、Rust 好きの方も是非。
で。
https://qiita.com/Nabetani/items/751ceaa60614dff6aec7 の Go版を Rust に移植した。
use std::cmp;
fn single(nums: &Vec<u8>) -> bool {
let max = nums.iter().max().unwrap();
let min = nums.iter().min().unwrap();
max == min
}
fn continuous(nums: &Vec<u8>) -> bool {
let mut s = nums.clone();
s.sort_unstable();
(0..(s.len() - 1)).all(|ix| {
let c = *s.iter().nth(ix).unwrap();
let n = *s.iter().nth(ix + 1).unwrap();
c + 1 == n
})
}
fn is_story_or_kind(cards: &Vec<String>) -> bool {
let rank = |s: &String| s.bytes().last().unwrap() - b'0';
let suit = |s: &String| s.bytes().next().unwrap();
let ranks = cards.iter().map(|c| rank(c)).collect::<Vec<u8>>();
let suits = cards.iter().map(|c| suit(c)).collect::<Vec<u8>>();
let is_kind = single(&ranks) && continuous(&suits);
let is_story = single(&suits) && continuous(&ranks);
is_kind || is_story
}
fn each_split(cards: &Vec<String>, mut f: Box<&mut FnMut(Vec<Vec<String>>)>) {
match cards.len() {
0 => {
f(Vec::<Vec<String>>::new());
}
1 => {}
2 | 3 => {
if is_story_or_kind(&cards) {
f(vec![cards.clone()]);
}
}
_ => {
let maxbits = 1i32 << (cards.len() - 1);
for bits in 1..maxbits {
let mut head: Vec<String> = vec![cards.iter().next().unwrap().clone()];
let mut tail: Vec<String> = Vec::<String>::new();
for b in 0..(cards.len() - 1) {
(if 0 != bits & (1 << b) {
&mut head
} else {
&mut tail
}).push(cards.iter().nth(b + 1).unwrap().to_string());
}
if !is_story_or_kind(&head) {
continue;
}
let mut closure = |mut hand: Vec<Vec<String>>| {
hand.push(head.clone());
f(hand);
};
each_split(&tail, Box::new(&mut closure))
}
}
}
}
fn score_of(hand: &Vec<Vec<String>>) -> i32 {
hand.iter()
.fold(0i32, |acc, g| acc + ((g.len() - 1) * (g.len() - 1)) as i32)
}
fn solve(src: &String) -> String {
let cards = src
.split(",")
.map(|x| x.to_string())
.collect::<Vec<String>>();
let mut score = 0i32;
{
let mut closure = |hand: Vec<Vec<String>>| {
score = cmp::max(score, score_of(&hand));
};
each_split(&cards, Box::new(&mut closure));
}
if score == 0 {
"-".to_string()
} else {
format!("{}", score)
}
}
fn test(num: i32, src: String, expected: String) {
let actual = solve(&src);
let okay = actual == expected;
println!(
"{}, {}, src: {}, act: {}, exp: {}",
num,
if okay { "ok" } else { "**NG**" },
src,
actual,
expected
);
}
macro_rules! test {
($n:expr, $x:expr, $y:expr) => {
test($n, $x.to_string(), $y.to_string());
};
}
fn main() {
test!(0, "A1,A2,A3,A4,B3,C3,D5,E5", "11");
test!(1, "A1,B2,C3,D4,E5,F6,G7,A8", "-");
test!(2, "A3,A5,A4,A6,A7,A1,A2,A8", "49");
test!(3, "G2,G1,A1,F1,C1,E1,B1,D1", "26");
test!(69, "A4,B4,F4,G4,E3,G3,E4,F3", "9");
test!(70, "A8,D8,D6,D7,C8,B8,E8,C7", "4");
}
いつもどおりテストデータの大半は省略。
で。
型が合わなくて一時間以上苦しんだ。
合わなかったのは、each_split
の第二引数。
最初は Box
を使わずに書いて、テンプレート引数の無限展開みたいになってコンパイルエラー。
Box
を使うことを思いついたものの、なにを「借用」して何を「mut
」すればいいのかわからず試行錯誤を繰り返した。
テストを通すところまでは持っていけたものの、まだ良くわかっていない。
わかっていないといえば、空の Vec<Vec<String>>
を作るときに Vec::<Vec<String>>::new()
と書いたんだけど、vec!
で作る方法があるのか無いのかわからなかった。
go からの移植なので、fold
とか map
がある幸せを噛み締めた。