[炼手Rust]扫雷

205 阅读2分钟

Hi,大家好,这里是炼手Rust专栏,我是xiaoK,今天来看个编程挑战:扫雷。

2x1_NSwitchDS_TheMinesweeperCrewBomberExpedition_image1600w.jpg

提问

扫雷的规则大家都不陌生,不过今天我们不是为了解决扫雷,而是按照游戏的规则标识雷的位置。

你的任务是将地雷数量添加到已完成的扫雷板中的空方块中,棋盘本身是一个由空(' ')或地雷('*')的方块组成的矩形。

对于每个空方格,计算与其相邻的地雷数量(水平、垂直、对角线)。如果空方格没有相邻的地雷,则将其留空。否则用相邻的地雷数量替换它。

例:

对于这样的 5 x 4 棋盘(此处用'·'字符表示空白以在屏幕上显示):
input:

·*·*·
··*··
··*··
·····

你需要转换成如下形式:
output:

1*3*1
13*31
·2*2·
·111·

将棋盘转换成我们玩儿扫雷游戏的那样,标识出周边地雷的位置。

模板:

pub fn annotate(minefield: &[&str]) -> Vec<String> { }

分析

对于输入字串,我们先按照换行符进行分割,然后按照字符char进行分割,将其转完成一个二维数组,然后遍历这个二维数组,对于每一个空位位置(即图中标识的'.'的位置),对其周边计算雷'*'的数量,得到的结果,设置给该位置即可。

解决方案

pub fn annotate(minefield: &[&str]) -> Vec<String> {
    if minefield.is_empty() { return vec![]; }
    let mut mines: Vec<Vec<char>> = minefield.iter().map(|s| (**s).chars().collect::<Vec<char>>()).collect();
    let rows = mines.len();
    let cols = mines[0].len();
    let def = ' ';

    (0..rows).for_each(|r| {
        (0..cols).for_each(|c| {
            if mines[r][c] == ' ' {
                let mut sum: u8 = 0;
                (r.saturating_sub(1)..=(r + 1)).for_each(|mine_row| {
                    if let Some(row) = mines.get(mine_row) {
                        (c.saturating_sub(1)..=(c + 1)).for_each(|mine_col| {
                            if *(row.get(mine_col).unwrap_or(&def)) == '*' {
                                sum += 1;
                            }
                        })
                    }
                });

                if sum != 0 {
                    mines[r][c] = char::from(b'0' + sum);
                }
            }
        });
    });

    mines.iter().map(|chs| chs.iter().collect::<String>()).collect()
}

Trick

  1. saturating_sub可以得到不越界的减法运算。在进行遍历的时候,可以使用这个方法进行不越界的遍历。
  2. rust中,有一种方法可以快速的得到String类型以及char类型的byte数组——使用前置b。例如:b'H'就是一个u8类型,而b"Hello"就是一个&[u8; 5]类型。