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

オフラインリアルタイムどう書く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 に移植した。

```rust: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` がある幸せを噛み締めた。