TypeScript 二维数组统计指定位置周围数字2个数
本文介绍一种健壮、可复用的 TypeScript 方法,用于统计 10×10 网格中某单元格周围(8 邻域)值为 2 的相邻格子数量,特别解决边界越界导致计数错误的问题。写扫雷类游戏的时候,经常要处理一个经典需求:给定一个坐标 (rowIndex, colIndex),快速统计它周围8个方向(上下左
本文介绍一种健壮、可复用的 TypeScript 方法,用于统计 10×10 网格中某单元格周围(8 邻域)值为 2 的相邻格子数量,特别解决边界越界导致计数错误的问题。
写扫雷类游戏的时候,经常要处理一个经典需求:给定一个坐标 (rowIndex, colIndex),快速统计它周围8个方向(上下左右加四个对角线)里值为2的格子个数——2一般代表冲击波。如果你一开始想到的是手写 if 条件逐个判断rowIndex-1、colIndex+1……那恭喜你,马上就会踩到边界处理的坑。网格边缘和角落的漏判、越界访问导致的 undefined 比较错误,几乎是新手必跳的陷阱。
其实有一个更优雅的思路:先安全提取所有邻域元素,再做过滤计数。这不仅让代码可读性翻倍,也从根本上解决了边界问题。下面直接上干货。
第一步:安全提取所有邻域元素(支持自定义半径)
先看这个 getNeighbors 函数,它通过清晰的范围计算,自动跳过自身位置和越界索引:
function getNeighbors( rowIndex: number, colIndex: number, source: Array [], radius: number = 1,): Array { const neighbors: T[] = []; if (!source.length) return neighbors; const numRows = source.length; const numCols = source[0].length; // 预计算有效行列范围,避免重复条件判断 const minRow = Math.max(0, rowIndex - radius); const maxRow = Math.min(numRows - 1, rowIndex + radius); const minCol = Math.max(0, colIndex - radius); const maxCol = Math.min(numCols - 1, colIndex + radius); for (let r = minRow; r <= maxRow; r++) { for (let c = minCol; c <= maxCol; c++) { // 跳过中心点自身 if (r !== rowIndex || c !== colIndex) { neighbors.push(source[r][c]); } } } return neighbors;}
这个实现的好处很明显:
- 绝无越界风险:用
Math.max/min卡住循环范围,不管网格多大都不用担心。 - 逻辑干净:没有深层 if 嵌套,一眼就能看明白要干什么。
- 扩展性强:
radius参数可以自由调整——比如要统计3×3区域内所有2,直接传radius=2就行。 - 类型安全:泛型
让返回数组类型和源数组保持一致,省去类型断言。
第二步:精准过滤并计数
提取到邻域列表后,算冲击波个数就只剩一行代码了:
const bombCount = getNeighbors(rowIndex, colIndex, grid) .filter(value => value === 2) .length;
注意:在 React 这类响应式环境里,要确保
grid是最新状态——比如在useCallback的依赖项里包含grid,防止闭包抓到旧值。
进阶优化:极致性能版(仅限 8 邻域)
如果你的场景明确限定为标准的8邻域,并且对执行效率有极致要求(比如每帧调用成千上万次),那可以用硬编码偏移 + 可选链(.)的方案。零循环,时间复杂度 O(1):
function get8Neighbors(rowIndex: number, colIndex: number, grid: Array []): T[] { return [ grid[rowIndex - 1]?.[colIndex - 1], grid[rowIndex - 1]?.[colIndex], grid[rowIndex - 1]?.[colIndex + 1], grid[rowIndex]?.[colIndex - 1], grid[rowIndex]?.[colIndex + 1], grid[rowIndex + 1]?.[colIndex - 1], grid[rowIndex + 1]?.[colIndex], grid[rowIndex + 1]?.[colIndex + 1], ].filter((v): v is T => v !== undefined); // 类型守卫,剔除 undefined}// 使用const bombCount = get8Neighbors(rowIndex, colIndex, grid).filter(v => v === 2).length;
这个版本利用可选链安全访问所有8个位置,filter 一步剔除 undefined,语义明确,性能拉满。
总结与最佳实践
| 方案 | 适用场景 | 边界安全性 | 可读性 | 扩展性 |
|---|---|---|---|---|
| 通用 getNeighbors | 主流推荐,兼顾鲁棒与清晰 | 完美 | 高 | 支持任意 radius |
| 硬编码 get8Neighbors | 极致性能要求、固定 8 邻域 | 安全(依赖 .) | 中等(偏移需记忆) | 固化 |
不管选哪个方案,有几点值得养成习惯:
- 在 React 等框架里,确保
grid状态被正确依赖; - 单元测试要覆盖边界用例——比如 (0,0)、(0,9)、(9,0)、(9,9) 这些角落;
- 不要在循环里直接修改
grid,保持函数纯净。
把邻域逻辑抽象出来之后,“边缘计数不准确”这个困扰基本就彻底告别了。扫雷的核心逻辑,可以写得既可靠又优雅。
