Golang中如何遍历二维列表并实现0到n的映射?

Golang中如何遍历二维列表并实现0到n的映射? 假设你有一个矩阵(二维数组),你选择了坐标 (x, y) 处的单元格,并且想要遍历它的8个邻居。遍历邻居的顺序并不重要。

选项1:

for i=-1, 1
for j=-1, 1
m[x + i ][y + j]

这将遍历邻居以及 (x,y) 单元格。

选项2: 定义一个常量二维数组 MASKS {{-1,-1}, {-1,0}, {-1,1},{0, -1}…

for mask in MASKS:
m[x + mask[0]][y + mask[1]]

选项3:<- 这里变得有趣了: 我想创建一个映射(仅使用二进制操作)将数字0到7与选项2中的掩码对应起来。也就是说,我希望我的代码看起来像:

for i = 0, 7:
m[x + io1 - io2][y + io3 - io4]

其中 o1,o2,o3,o4 是使用 & 和 | 的二进制操作。

如何找到这样的映射?这个技巧能否推广到超过8个邻居的算法中?

请注意,我相当确定这是可以做到的,正如我直觉上假设的那样。


更多关于Golang中如何遍历二维列表并实现0到n的映射?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我对此进行了一些尝试,发现很难理解如何将8位排列映射到三组数据,其中两组包含三个成员,一组包含两个成员。具体来说,(0,0)的相邻点:

  • 第一组:(1,-1), (1,0), (1,1)
  • 第二组:(-1,-1), (-1,0), (-1,1)
  • 第三组:(0,-1), (0,1)

我回复只是因为如果有人找到解决方案,我希望收到通知!微笑

更多关于Golang中如何遍历二维列表并实现0到n的映射?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在 Go 语言中,遍历二维数组(矩阵)的 8 个邻居时,可以通过二进制操作将索引 0 到 7 映射到偏移量,从而避免使用预定义的掩码数组。这种方法利用位运算来动态计算邻居坐标的偏移量,适用于 8 个邻居的情况,并且可以推广到其他类似场景,例如 4 个邻居(仅上下左右)或更多方向。

以下是实现代码示例。我们假设矩阵 m 是一个二维整数数组,坐标 (x, y) 是有效的,并且我们处理边界检查以避免越界访问。映射的核心思想是将索引 i(0 到 7)分解为两个二进制位,分别表示行和列的偏移方向(-1、0 或 1),但注意 8 个邻居包括所有组合,除了 (0,0) 自身。

对于 8 个邻居,我们可以将索引 i 视为一个 3 位数字(因为 2^3 = 8),并使用位操作提取行和列的偏移。具体来说:

  • 行偏移:使用 (i >> 2) & 1(i >> 1) & 1 的组合来生成 -1、0 或 1。
  • 列偏移:使用 (i >> 0) & 1(i >> 1) & 1 的组合。

但更简单的方法是直接使用一个查找表或计算偏移,因为 8 个邻居的偏移是固定的:(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)。我们可以通过索引 i 计算行和列偏移:

  • 行偏移 = (i / 3) - 1,但注意 8 个邻居不是均匀分布的;更好的方式是用位操作模拟。

实际上,一个常见技巧是将索引 i 从 0 到 7 映射到行偏移 dx 和列偏移 dy,其中:

  • dx = (i / 3) - 1dy = (i % 3) - 1,但这样会包括 (0,0),需要跳过。
  • 或者,我们可以定义一个函数,使用位操作直接计算。

以下是使用位操作实现映射的 Go 代码示例。我们通过将索引 i 的二进制位分解来生成偏移,确保不包括 (0,0):

package main

import "fmt"

func main() {
    // 示例矩阵,假设为 3x3 整数矩阵
    m := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
    x, y := 1, 1 // 中心单元格 (1,1),值为 5

    // 遍历 0 到 7 的索引,使用位操作映射到邻居偏移
    for i := 0; i < 8; i++ {
        // 使用位操作计算行偏移 dx 和列偏移 dy
        // 将 i 视为 3 位二进制数: b2 b1 b0
        // dx = (b2 << 1) | b1 - 1? 更简单的方法:直接计算偏移表,但用位操作模拟
        // 实际上,我们可以这样定义:
        // dx = (i >> 2) & 1? 不直接,改用以下方法:
        // 对于 8 个邻居,偏移可以基于 i 的二进制表示生成
        // 但为了简化,这里使用一个标准方法:将 i 映射到预定义的 8 个方向,但用计算代替数组

        // 一个可行的映射:使用 i 生成 dx 和 dy,其中 dx 和 dy 各为 -1, 0, 1
        // 通过 i 的位组合:令 a = (i >> 2) & 1, b = (i >> 1) & 1, c = i & 1
        // 但更直接的方法是使用除法和取模,跳过 (0,0)
        dx := (i / 3) - 1
        dy := (i % 3) - 1
        if dx == 0 && dy == 0 {
            continue // 跳过自身单元格
        }
        nx, ny := x+dx, y+dy
        // 检查边界
        if nx >= 0 && nx < len(m) && ny >= 0 && ny < len(m[0]) {
            fmt.Printf("Neighbor at (%d, %d): %d\n", nx, ny, m[nx][ny])
        }
    }
}

上述代码使用除法和取模操作来生成偏移,但这不是纯位操作。如果要完全使用位操作(如 & 和 |),我们可以通过索引 i 的二进制位来直接计算 dxdy。例如,将 i 视为一个 3 位数字,位为 b2 b1 b0,然后:

  • dx = (b2 << 1) | b1 - 1?这可能会产生 -1、0、1、2 等,不精确。

一个更精确的位操作映射是使用查找表,但用计算代替。实际上,对于 8 个邻居,一个标准位操作技巧是:

  • 定义 dx = ((i & 4) >> 2) - ((i & 2) >> 1)?这比较复杂。

由于问题要求使用二进制操作(& 和 |)实现映射,并且推广到更多邻居,我们可以设计一个通用函数。但注意,8 个邻居的偏移是固定的,位操作可能不是最直接的方式;通常使用掩码数组更清晰。如果坚持用位操作,这里是一个示例,通过位组合生成偏移:

package main

import "fmt"

func main() {
    m := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }
    x, y := 1, 1

    for i := 0; i < 8; i++ {
        // 使用位操作计算 dx 和 dy
        // 将 i 的位分解:令 a = (i >> 2) & 1, b = (i >> 1) & 1, c = i & 1
        // 然后定义 dx = a + b - 1? 不通用。改用以下方法:
        // 实际上,一个简单映射:dx = (i >> 1) & 1? 不工作。
        // 更可靠的方法:使用预计算,但用位操作模拟方向。
        // 由于 8 个邻居的偏移是 (-1,-1) 到 (1,1),不包括 (0,0),我们可以用:
        dx := (i>>1)&1 - (i>>2)&1 // 示例计算,可能不准确
        dy := i&1 - (i>>1)&1      // 类似
        // 但上述计算可能不覆盖所有情况;实际上,这需要更复杂的位操作。

        // 一个可行的纯位操作映射(针对 8 邻居):
        // 定义 dx 和 dy 基于 i 的位:令 high = i >> 2, low = i & 3
        // 然后 dx = (high & 1) - ((high >> 1) & 1)? 不直接。
        // 由于复杂性,通常推荐使用掩码数组。但为满足要求,这里是一个近似:
        // 使用 i 生成 dx 和 dy,例如:
        dx := ((i & 4) >> 2) - ((i & 2) >> 1)
        dy := ((i & 2) >> 1) - (i & 1)
        // 调整以覆盖 -1,0,1
        // 注意:这只是一个示例,可能不精确;实际中需要测试所有 i。

        nx, ny := x+dx, y+dy
        if nx >= 0 && nx < len(m) && ny >= 0 && ny < len(m[0]) {
            fmt.Printf("Neighbor at (%d, %d): %d\n", nx, ny, m[nx][ny])
        }
    }
}

注意:上述位操作示例可能不精确,因为 8 个邻居的映射需要确保每个 i 对应唯一的偏移对,且覆盖所有 8 个方向。在实际应用中,使用预定义的掩码数组(如选项2)更可靠和易读。但如果目标是纯位操作,可能需要更复杂的位掩码和条件调整。

对于推广到超过 8 个邻居(例如 24 个邻居在 3D 网格),类似位操作可能更复杂,通常使用循环或查找表更实用。位操作的优势在于性能,但可能牺牲可读性。在 Go 中,建议根据具体需求选择方法。

回到顶部