当前位置: 首页 > news >正文

Rust 练习册 :Minesweeper与二维数组处理

扫雷游戏是微软从1990年代开始在Windows系统中内置的经典游戏之一。在游戏中,玩家需要根据数字提示推理出地雷的位置。在 Exercism 的 “minesweeper” 练习中,我们需要实现扫雷游戏的提示数字生成算法。这不仅能帮助我们掌握二维数组处理和邻域计算,还能深入学习Rust中的字符串处理、边界检查和算法实现。

什么是扫雷提示生成?

扫雷提示生成是扫雷游戏中的核心算法之一。给定一个包含地雷位置的网格,算法需要为每个空格计算周围8个方向内地雷的数量,并用数字标记。如果某个空格周围没有地雷,则保持空白。

规则如下:

  1. 地雷用’*'表示
  2. 空格用’ '表示
  3. 数字表示该位置周围8个格子内地雷的数量
  4. 如果周围没有地雷,保持空白

例如,对于以下输入:

 * * *  * * 

输出应该是:

1*3*1
13*31
1*3*1

扫雷提示生成在以下领域有应用:

  1. 游戏开发:扫雷游戏实现
  2. 图像处理:邻域操作和卷积运算
  3. 数据挖掘:邻近点分析
  4. GIS系统:空间邻域分析

让我们先看看练习提供的函数签名:

pub fn annotate(minefield: &[&str]) -> Vec<String> {unimplemented!("\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{:#?}\n", minefield);
}

我们需要实现一个函数,为给定的地雷网格生成提示数字。

设计分析

1. 核心要求

  1. 二维网格处理:处理字符串切片表示的二维网格
  2. 邻域计算:计算每个位置周围8个方向的邻居
  3. 地雷计数:统计邻居中的地雷数量
  4. 输出格式:生成带提示数字的字符串向量

2. 技术要点

  1. 边界检查:确保邻居位置在网格范围内
  2. 字符处理:处理不同类型的字符(地雷、空格、数字)
  3. 数组索引:正确处理二维数组的行列索引
  4. 性能优化:避免重复计算

完整实现

1. 基础实现

pub fn annotate(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();let mut result = Vec::with_capacity(rows);for i in 0..rows {let mut row_string = String::with_capacity(cols);for j in 0..cols {if minefield[i].chars().nth(j).unwrap() == '*' {row_string.push('*');} else {let count = count_mines(minefield, i, j);if count == 0 {row_string.push(' ');} else {row_string.push_str(&count.to_string());}}}result.push(row_string);}result
}fn count_mines(minefield: &[&str], row: usize, col: usize) -> u8 {let rows = minefield.len();let cols = minefield[0].len();let mut count = 0;// 检查周围的8个位置for i in row.saturating_sub(1)..=row + 1 {for j in col.saturating_sub(1)..=col + 1 {// 确保在边界内且不是当前位置if i < rows && j < cols && (i != row || j != col) {if minefield[i].chars().nth(j).unwrap() == '*' {count += 1;}}}}count
}

2. 优化实现

pub fn annotate(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();let mut result = Vec::with_capacity(rows);// 将字符串转换为字符向量以提高访问效率let char_grid: Vec<Vec<char>> = minefield.iter().map(|row| row.chars().collect()).collect();for i in 0..rows {let mut row_string = String::with_capacity(cols);for j in 0..cols {if char_grid[i][j] == '*' {row_string.push('*');} else {let count = count_mines(&char_grid, i, j);if count == 0 {row_string.push(' ');} else {row_string.push((b'0' + count) as char);}}}result.push(row_string);}result
}fn count_mines(grid: &[Vec<char>], row: usize, col: usize) -> u8 {let rows = grid.len();let cols = grid[0].len();let mut count = 0;// 检查周围的8个位置for i in row.saturating_sub(1)..=usize::min(row + 1, rows - 1) {for j in col.saturating_sub(1)..=usize::min(col + 1, cols - 1) {// 确保不是当前位置if i != row || j != col {if grid[i][j] == '*' {count += 1;}}}}count
}

3. 函数式实现

pub fn annotate(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();// 将字符串转换为字符向量以提高访问效率let char_grid: Vec<Vec<char>> = minefield.iter().map(|row| row.chars().collect()).collect();(0..rows).map(|i| {(0..cols).map(|j| {if char_grid[i][j] == '*' {'*'} else {let count = count_mines(&char_grid, i, j);if count == 0 {' '} else {(b'0' + count) as char}}}).collect()}).collect()
}fn count_mines(grid: &[Vec<char>], row: usize, col: usize) -> u8 {let rows = grid.len();let cols = grid[0].len();(row.saturating_sub(1)..=usize::min(row + 1, rows - 1)).flat_map(|i| {(col.saturating_sub(1)..=usize::min(col + 1, cols - 1)).filter_map(move |j| {if i != row || j != col {Some((i, j))} else {None}})}).filter(|&(i, j)| grid[i][j] == '*').count() as u8
}

测试用例分析

通过查看测试用例,我们可以更好地理解需求:

#[test]
fn no_rows() {#[rustfmt::skip]run_test(&[]);
}

空网格应该返回空向量。

#[test]
fn no_columns() {#[rustfmt::skip]run_test(&["",]);
}

只有空行的网格应该返回包含空字符串的向量。

#[test]
fn no_mines() {#[rustfmt::skip]run_test(&["   ","   ","   ",]);
}

没有地雷的网格应该全部是空格。

#[test]
fn board_with_only_mines() {#[rustfmt::skip]run_test(&["***","***","***",]);
}

全是地雷的网格应该保持不变。

#[test]
fn mine_surrounded_by_spaces() {#[rustfmt::skip]run_test(&["111","1*1","111",]);
}

地雷周围应该显示数字提示。

#[test]
fn space_surrounded_by_mines() {#[rustfmt::skip]run_test(&["***","*8*","***",]);
}

被地雷包围的空格应该显示8。

#[test]
fn cross() {#[rustfmt::skip]run_test(&[" 2*2 ","25*52","*****","25*52"," 2*2 ",]);
}

复杂的交叉模式应该正确计算。

性能优化版本

考虑性能的优化实现:

pub fn annotate(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();// 预分配结果向量let mut result = Vec::with_capacity(rows);// 使用字节处理提高性能let byte_grid: Vec<&[u8]> = minefield.iter().map(|s| s.as_bytes()).collect();for i in 0..rows {let mut row_bytes = Vec::with_capacity(cols);for j in 0..cols {if byte_grid[i][j] == b'*' {row_bytes.push(b'*');} else {let count = count_mines_bytes(&byte_grid, i, j);if count == 0 {row_bytes.push(b' ');} else {row_bytes.push(b'0' + count);}}}// 安全地从字节创建字符串result.push(String::from_utf8(row_bytes).unwrap());}result
}fn count_mines_bytes(grid: &[&[u8]], row: usize, col: usize) -> u8 {let rows = grid.len();let cols = grid[0].len();let mut count = 0;let start_row = row.saturating_sub(1);let end_row = usize::min(row + 1, rows - 1);let start_col = col.saturating_sub(1);let end_col = usize::min(col + 1, cols - 1);for i in start_row..=end_row {for j in start_col..=end_col {if i != row || j != col {if grid[i][j] == b'*' {count += 1;}}}}count
}// 使用预计算的计数网格版本
pub fn annotate_precalculated(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();// 创建计数网格let mut count_grid = vec![vec![0u8; cols]; rows];// 使用字节处理提高性能let byte_grid: Vec<&[u8]> = minefield.iter().map(|s| s.as_bytes()).collect();// 首先标记所有地雷并更新邻居计数for i in 0..rows {for j in 0..cols {if byte_grid[i][j] == b'*' {// 更新周围8个位置的计数for di in -1..=1 {for dj in -1..=1 {if di == 0 && dj == 0 {continue;}let ni = i as i32 + di;let nj = j as i32 + dj;if ni >= 0 && ni < rows as i32 && nj >= 0 && nj < cols as i32 {let ni = ni as usize;let nj = nj as usize;if byte_grid[ni][nj] != b'*' {count_grid[ni][nj] += 1;}}}}}}}// 生成结果(0..rows).map(|i| {(0..cols).map(|j| {if byte_grid[i][j] == b'*' {'*'} else {let count = count_grid[i][j];if count == 0 {' '} else {(b'0' + count) as char}}}).collect()}).collect()
}

错误处理和边界情况

考虑更多边界情况的实现:

#[derive(Debug, PartialEq)]
pub enum MinesweeperError {InvalidInput,InconsistentRowLength,
}pub fn annotate_safe(minefield: &[&str]) -> Result<Vec<String>, MinesweeperError> {// 检查输入有效性if minefield.is_empty() {return Ok(vec![]);}// 检查行长度一致性let first_row_len = minefield[0].len();if minefield.iter().any(|row| row.len() != first_row_len) {return Err(MinesweeperError::InconsistentRowLength);}// 检查字符有效性for row in minefield {for c in row.chars() {if c != ' ' && c != '*' {return Err(MinesweeperError::InvalidInput);}}}Ok(annotate(minefield))
}pub fn annotate(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();let char_grid: Vec<Vec<char>> = minefield.iter().map(|row| row.chars().collect()).collect();(0..rows).map(|i| {(0..cols).map(|j| {if char_grid[i][j] == '*' {'*'} else {let count = count_mines(&char_grid, i, j);if count == 0 {' '} else {(b'0' + count) as char}}}).collect()}).collect()
}fn count_mines(grid: &[Vec<char>], row: usize, col: usize) -> u8 {let rows = grid.len();let cols = grid[0].len();(row.saturating_sub(1)..=usize::min(row + 1, rows - 1)).flat_map(|i| {(col.saturating_sub(1)..=usize::min(col + 1, cols - 1)).filter_map(move |j| {if i != row || j != col {Some((i, j))} else {None}})}).filter(|&(i, j)| grid[i][j] == '*').count() as u8
}

扩展功能

基于基础实现,我们可以添加更多功能:

pub struct Minesweeper {grid: Vec<Vec<char>>,rows: usize,cols: usize,
}impl Minesweeper {pub fn new(minefield: &[&str]) -> Self {let rows = minefield.len();let cols = if rows > 0 { minefield[0].len() } else { 0 };let grid: Vec<Vec<char>> = minefield.iter().map(|row| row.chars().collect()).collect();Minesweeper { grid, rows, cols }}pub fn annotate(&self) -> Vec<String> {if self.rows == 0 {return vec![];}(0..self.rows).map(|i| {(0..self.cols).map(|j| {if self.grid[i][j] == '*' {'*'} else {let count = self.count_mines(i, j);if count == 0 {' '} else {(b'0' + count) as char}}}).collect()}).collect()}fn count_mines(&self, row: usize, col: usize) -> u8 {(row.saturating_sub(1)..=usize::min(row + 1, self.rows - 1)).flat_map(|i| {(col.saturating_sub(1)..=usize::min(col + 1, self.cols - 1)).filter_map(move |j| {if i != row || j != col {Some((i, j))} else {None}})}).filter(|&(i, j)| self.grid[i][j] == '*').count() as u8}// 获取特定位置的值pub fn get_cell(&self, row: usize, col: usize) -> Option<char> {if row < self.rows && col < self.cols {Some(self.grid[row][col])} else {None}}// 获取周围邻居的信息pub fn get_neighbors(&self, row: usize, col: usize) -> Vec<(usize, usize, char)> {(row.saturating_sub(1)..=usize::min(row + 1, self.rows - 1)).flat_map(|i| {(col.saturating_sub(1)..=usize::min(col + 1, self.cols - 1)).filter_map(move |j| {if i != row || j != col {Some((i, j, self.grid[i][j]))} else {None}})}).collect()}// 计算整个网格的地雷总数pub fn count_total_mines(&self) -> usize {self.grid.iter().flat_map(|row| row.iter()).filter(|&&c| c == '*').count()}// 验证网格是否有效pub fn is_valid(&self) -> bool {if self.rows == 0 {return true;}let first_row_len = self.cols;self.grid.iter().all(|row| row.len() == first_row_len)}
}// 便利函数
pub fn annotate(minefield: &[&str]) -> Vec<String> {Minesweeper::new(minefield).annotate()
}// 支持不同提示样式的版本
pub struct MinesweeperConfig {pub mine_char: char,pub empty_char: char,pub number_style: NumberStyle,
}#[derive(Clone)]
pub enum NumberStyle {Digits,      // 使用数字 1-8Letters,     // 使用字母 a-hRoman,       // 使用罗马数字 I-VIII
}impl Minesweeper {pub fn annotate_with_config(&self, config: &MinesweeperConfig) -> Vec<String> {if self.rows == 0 {return vec![];}(0..self.rows).map(|i| {(0..self.cols).map(|j| {if self.grid[i][j] == '*' {config.mine_char} else {let count = self.count_mines(i, j);if count == 0 {config.empty_char} else {match config.number_style {NumberStyle::Digits => (b'0' + count) as char,NumberStyle::Letters => (b'a' + count - 1) as char,NumberStyle::Roman => match count {1 => 'I',2 => 'I',3 => 'I',4 => 'I',5 => 'V',6 => 'V',7 => 'V',8 => 'V',_ => ' ',},// 简化实现}}}}).collect()}).collect()}
}

实际应用场景

扫雷提示生成在实际开发中有以下应用:

  1. 游戏开发:扫雷游戏和其他益智游戏
  2. 图像处理:邻域操作和卷积运算
  3. 数据挖掘:邻近点分析和聚类算法
  4. GIS系统:空间邻域分析和热点检测
  5. 计算机视觉:边缘检测和特征提取
  6. 科学计算:有限元分析和网格处理
  7. 社交网络:社交关系分析和推荐系统
  8. 生物信息学:基因序列分析和蛋白质结构预测

算法复杂度分析

  1. 时间复杂度:O(rows × cols × 8) = O(rows × cols)

    • 需要遍历网格中的每个位置,每个位置最多检查8个邻居
  2. 空间复杂度:O(rows × cols)

    • 需要存储结果网格和可能的中间表示

与其他实现方式的比较

// 使用递归的实现
pub fn annotate_recursive(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();let char_grid: Vec<Vec<char>> = minefield.iter().map(|row| row.chars().collect()).collect();fn process_cell(grid: &[Vec<char>],result: &mut Vec<Vec<char>>,row: usize,col: usize,rows: usize,cols: usize,) {if row >= rows || col >= cols {return;}if grid[row][col] == '*' {result[row][col] = '*';} else {let count = count_mines(grid, row, col);result[row][col] = if count == 0 { ' ' } else { (b'0' + count) as char };}}fn count_mines(grid: &[Vec<char>], row: usize, col: usize) -> u8 {let rows = grid.len();let cols = grid[0].len();(row.saturating_sub(1)..=usize::min(row + 1, rows - 1)).flat_map(|i| {(col.saturating_sub(1)..=usize::min(col + 1, cols - 1)).filter_map(move |j| {if i != row || j != col {Some((i, j))} else {None}})}).filter(|&(i, j)| grid[i][j] == '*').count() as u8}let mut result: Vec<Vec<char>> = vec![vec![' '; cols]; rows];for i in 0..rows {for j in 0..cols {process_cell(&char_grid, &mut result, i, j, rows, cols);}}result.into_iter().map(|row| row.into_iter().collect()).collect()
}// 使用迭代器链的函数式实现
pub fn annotate_functional(minefield: &[&str]) -> Vec<String> {minefield.iter().enumerate().map(|(i, row)| {row.chars().enumerate().map(|(j, cell)| {if cell == '*' {'*'} else {let count = minefield.iter().enumerate().flat_map(|(di, d_row)| {d_row.chars().enumerate().filter_map(move |(dj, d_cell)| {// 检查是否在周围8个位置内且不是当前位置if (di as i32 - i as i32).abs() <= 1&& (dj as i32 - j as i32).abs() <= 1&& (di != i || dj != j)&& d_cell == '*'{Some(())} else {None}})}).count();if count == 0 {' '} else {(b'0' + count as u8) as char}}}).collect()}).collect()
}// 使用第三方库的实现
// [dependencies]
// ndarray = "0.15"use ndarray::Array2;pub fn annotate_ndarray(minefield: &[&str]) -> Vec<String> {if minefield.is_empty() {return vec![];}let rows = minefield.len();let cols = minefield[0].len();// 创建二维数组let mut grid = Array2::<char>::default((rows, cols));for (i, row) in minefield.iter().enumerate() {for (j, ch) in row.chars().enumerate() {grid[[i, j]] = ch;}}// 创建计数数组let mut counts = Array2::<u8>::zeros((rows, cols));// 计算每个位置的地雷数for i in 0..rows {for j in 0..cols {if grid[[i, j]] == '*' {counts[[i, j]] = 0; // 地雷位置保持为0或用特殊标记} else {let count = (i.saturating_sub(1)..=usize::min(i + 1, rows - 1)).flat_map(|ni| {(j.saturating_sub(1)..=usize::min(j + 1, cols - 1)).map(move |nj| (ni, nj))}).filter(|&(ni, nj)| (ni != i || nj != j) && grid[[ni, nj]] == '*').count() as u8;counts[[i, j]] = count;}}}// 生成结果(0..rows).map(|i| {(0..cols).map(|j| {if grid[[i, j]] == '*' {'*'} else {let count = counts[[i, j]];if count == 0 {' '} else {(b'0' + count) as char}}}).collect()}).collect()
}

总结

通过 minesweeper 练习,我们学到了:

  1. 二维数组处理:掌握了二维网格的表示和处理方法
  2. 邻域计算:学会了计算网格中每个位置的邻居
  3. 边界检查:理解了如何安全地处理数组边界
  4. 性能优化:了解了不同实现方式的性能特点
  5. 字符处理:深入理解了字符和字节级别的处理
  6. 算法设计:学会了设计网格处理算法

这些技能在实际开发中非常有用,特别是在游戏开发、图像处理、数据挖掘等场景中。扫雷提示生成虽然是一个具体的游戏算法问题,但它涉及到了二维数组处理、邻域计算、边界处理等许多核心概念,是学习Rust实用编程的良好起点。

通过这个练习,我们也看到了Rust在处理二维数据和字符数据方面的强大能力,以及如何用安全且高效的方式实现经典算法。这种结合了安全性和性能的语言特性正是Rust的魅力所在。

http://www.dtcms.com/a/582216.html

相关文章:

  • Flink CDC「Data Pipeline」定义与参数速查
  • 电子烟花:科技点亮夜空的艺术
  • Anatomy-guided Pathology Segmentation
  • 广州建设工程合同备案系统网站做一个网站需要多少费用
  • 内存区域划分——垃圾回收
  • 网站建设可行性分析网站开发需求分析用的图
  • Android 无侵入式数据采集:从手动埋点到字节码插桩的演进之路
  • 一致性哈希和普通哈希有什么区别
  • vue 三种类型的插槽
  • TCP的核心特性精讲(上篇)
  • 河源市企业网站seo价格商城网站策划书
  • Spark-3.5.7文档5 - Spark Streaming 编程指南
  • 北京网站关键词优化推荐徐州列表网
  • Spring 事务管理 Transaction rolled back because it has been marked as rollback-only
  • git不想被添加的文件加入到了列表中如何去掉
  • 网关开发笔记
  • 不备案怎么做淘宝客网站吗网站的视频怎么下载
  • 贵阳市住房和城乡建设部网站北京有几个区几个县
  • 【笔记】修复 ComfyUI 启动 ImportError: cannot import name ‘cached_download‘ 错误
  • 长沙网站优化页面学校网站建设工作
  • 昆明企业做网站黎城网站建设
  • 在vue3+uniapp+vite中挂载全局属性方法
  • 地理信息科学 vs 测绘工程:专业区别与就业前景
  • ​​Linux环境下的C语言编程(十六)
  • 淘宝购物返利网站开发基层建设杂志网站
  • 某多多 Redis 面试相关知识点总结
  • 【STM32】知识点介绍三:哈希算法详解
  • Effective STL第8条: 切勿创建包含auto_ptr的容器对象
  • 使用DrissionPage实现虚拟货币市场数据智能爬取
  • 零基础入门C语言之预处理详解