英文:
New to Golang - Confused about pointers, please help to convert c++ snippet to go
问题
我是你的中文翻译助手,以下是你要翻译的内容:
我刚开始学习golang,并且正在慢慢学习。为了学习,我选择将这段算法转换为golang,算法链接为"http://www.geeksforgeeks.org/backtracking-set-7-suduku/"。
我已经尽力了,但是golang的指针比C++的指针更加令人困惑。
我尝试了很多不同的方法来使它正常工作,但指针的问题仍然让我感到困扰。我就是无法让下面提到的两个函数正常工作。有时输出是"no solution found",有时只输出未改变的网格。
请问有人能修复SolveSudoku()和FindUnassignedLocation()函数(以及其他可能的问题),并解释如何在GO中使用引用和指针吗?
我也尝试阅读GO网站上的一些文档,但实际上并不是很好,而且我经验不足,无法理解那里提供的非常细微的内容。
如果有人能提出一些建议,改进这段代码,有什么错误或者漏洞,我将非常感激。
package main
import "fmt"
const (
UNASSIGNED = 0
N = 9
)
func SolveSudoku(grid [N][N]int) bool {
var row, col int
if !FindUnassignedLocation(grid, &row, &col) {
return true
}
for num := 1; num <= 9; num++ {
if isSafe(grid, row, col, num) {
grid[row][col] = num
if SolveSudoku(grid) {
return true
}
grid[row][col] = UNASSIGNED
}
}
return false
}
func FindUnassignedLocation(grid [N][N]int, row *int, col *int) bool {
for *row = 0; *row < N; *row++ {
for *col = 0; *col < N; *col++ {
if grid[*row][*col] == UNASSIGNED {
return true
}
}
}
return false
}
func UsedInRow(grid [N][N]int, row int, num int) bool {
for col := 0; col < N; col++ {
if grid[row][col] == num {
return true
}
}
return false
}
func UsedInCol(grid [N][N]int, col int, num int) bool {
for row := 0; row < N; row++ {
if grid[row][col] == num {
return true
}
}
return false
}
func UsedInBox(grid [N][N]int, boxStartRow int, boxStartCol int, num int) bool {
for row := 0; row < 3; row++ {
for col := 0; col < 3; col++ {
if grid[row+boxStartRow][col+boxStartCol] == num {
return true
}
}
}
return false
}
func isSafe(grid [N][N]int, row int, col int, num int) bool {
return !UsedInRow(grid, row, num) && !UsedInCol(grid, col, num) && !UsedInBox(grid, row-row%3, col-col%3, num)
}
func printGrid(grid [N][N]int) {
for row := 0; row < N; row++ {
for col := 0; col < N; col++ {
fmt.Printf("%2d", grid[row][col])
}
fmt.Printf("\n")
}
}
func main() {
var grid = [N][N]int{
[N]int{3, 0, 6, 5, 0, 8, 4, 0, 0},
[N]int{5, 2, 0, 0, 0, 0, 0, 0, 0},
[N]int{0, 8, 7, 0, 0, 0, 0, 3, 1},
[N]int{0, 0, 3, 0, 1, 0, 0, 8, 0},
[N]int{9, 0, 0, 8, 6, 3, 0, 0, 5},
[N]int{0, 5, 0, 0, 9, 0, 6, 0, 0},
[N]int{1, 3, 0, 0, 0, 0, 2, 5, 0},
[N]int{0, 0, 0, 0, 0, 0, 0, 7, 4},
[N]int{0, 0, 5, 2, 0, 6, 3, 0, 0},
}
if SolveSudoku(grid) == true {
printGrid(grid)
} else {
fmt.Printf("No solution exists")
}
return
}
希望对你有帮助!如果有任何问题,请随时问我。
英文:
I am new to golang, and am slowly learning. To learn I chose to convert this piece of algorithm to golang "http://www.geeksforgeeks.org/backtracking-set-7-suduku/".
I have done my best, but golang pointers confuse more than c++ ones.
I tried a lot of different ways to get it to work but still the pointer stuff keeps getting on my nerves. I just can't get the below mentioned two functions to work properly. Sometimes the output is - no solution found. And sometimes, it just outputs the unchanged grid.
Can someone please fix the SolveSudoku() and FindUnassignedLocation() functions (and anything else, if any) and explain how to use references and pointers in GO.
I also tried reading some documentation on the GO website but that isn't that great really and I am not experienced enough to understand the extremely small stuff given there.
I will also be grateful if someone suggests a few more things that can be improved in this code, bugs? , errors?, anything.
package main
import "fmt"
const (
UNASSIGNED = 0
N = 9
)
func SolveSudoku(grid [N][N]int) bool {
var row, col int
if !FindUnassignedLocation(grid, row, col) {
return true
}
for num := 1; num <= 9; num++ {
if isSafe(grid, row, col, num) {
grid[row][col] = num
if SolveSudoku(grid) {
return true
}
grid[row][col] = UNASSIGNED
}
}
return false
}
func FindUnassignedLocation(grid [N][N]int, row int, col int) bool {
for row = 0; row < N; row++ {
for col = 0; col < N; col++ {
if grid[row][col] == UNASSIGNED {
return true
}
}
}
return false
}
func UsedInRow(grid [N][N]int, row int, num int) bool {
for col := 0; col < N; col++ {
if grid[row][col] == num {
return true
}
}
return false
}
func UsedInCol(grid [N][N]int, col int, num int) bool {
for row := 0; row < N; row++ {
if grid[row][col] == num {
return true
}
}
return false
}
func UsedInBox(grid [N][N]int, boxStartRow int, boxStartCol int, num int) bool {
for row := 0; row < 3; row++ {
for col := 0; col < 3; col++ {
if grid[row+boxStartRow][col+boxStartCol] == num {
return true
}
}
}
return false
}
func isSafe(grid [N][N]int, row int, col int, num int) bool {
return !UsedInRow(grid, row, num) && !UsedInCol(grid, col, num) && !UsedInBox(grid, row-row%3, col-col%3, num)
}
func printGrid(grid [N][N]int) {
for row := 0; row < N; row++ {
for col := 0; col < N; col++ {
fmt.Printf("%2d", grid[row][col])
}
fmt.Printf("\n")
}
}
func main() {
var grid = [N][N]int{
[N]int{3, 0, 6, 5, 0, 8, 4, 0, 0},
[N]int{5, 2, 0, 0, 0, 0, 0, 0, 0},
[N]int{0, 8, 7, 0, 0, 0, 0, 3, 1},
[N]int{0, 0, 3, 0, 1, 0, 0, 8, 0},
[N]int{9, 0, 0, 8, 6, 3, 0, 0, 5},
[N]int{0, 5, 0, 0, 9, 0, 6, 0, 0},
[N]int{1, 3, 0, 0, 0, 0, 2, 5, 0},
[N]int{0, 0, 0, 0, 0, 0, 0, 7, 4},
[N]int{0, 0, 5, 2, 0, 6, 3, 0, 0},
}
if SolveSudoku(grid) == true {
printGrid(grid)
} else {
fmt.Printf("No solution exists")
}
return
}
答案1
得分: 5
你的问题并不是你不理解指针。在Go中,指针的工作方式与C++大致相同(除了Go具有逃逸分析和垃圾收集等功能,你不必担心悬空或无效的指针,但代价是没有指针运算)。
你的问题在于你误解了C++和Go中数组的区别。在C++中,数组实际上是一个指针,语法上是一个别名。int a[9]
和int *a
是相同的类型,静态大小和自由初始化只是一个很酷的编译器技巧。访问数组是指针算术后跟解引用的花哨语法——正如前面提到的,Go没有这个功能。
在Go中,数组是值而不是指针。当你有一个接受a [9]int
的函数时,你实际上是告诉编译器复制九个整数值,而不是指向内存中九个整数值的位置的指针。将func(a [2]int)
视为编写func(a1, a2 int)
的一种简便方式。
这也导致了另一个微妙的代码差异,在C++中,int a[9][9]
是指向九个指向九个整数的指针的指针。在Go中,它是一个字面上的、连续的RMO存储的9x9整数块。
在Go中有两个简单的解决方案:
-
使用数组指针,例如
grid *[N][N]int
。这样做是可以的,但访问和存储数组中的元素有点不够清晰。你将不得不使用(*grid)[i][j]
来显式解引用指针,这看起来很丑陋,有点难以阅读。 -
使用切片。这是更好的选择,也是更符合Go风格的方式。它还避免了那个讨厌的全局常量在各个地方出现。不过,你会牺牲一些关于列大小的a priori保证。
我将使用切片重写一些方法,并添加一些注释,其余部分由你完成:
func main() {
// 我们可以省略每行的 []int,Go会自动推断
var grid = [][]int{
{3, 0, 6, 5, 0, 8, 4, 0, 0},
{5, 2, 0, 0, 0, 0, 0, 0, 0},
{0, 8, 7, 0, 0, 0, 0, 3, 1},
{0, 0, 3, 0, 1, 0, 0, 8, 0},
{9, 0, 0, 8, 6, 3, 0, 0, 5},
{0, 5, 0, 0, 9, 0, 6, 0, 0},
{1, 3, 0, 0, 0, 0, 2, 5, 0},
{0, 0, 0, 0, 0, 0, 0, 7, 4},
{0, 0, 5, 2, 0, 6, 3, 0, 0},
}
// == true 是多余的
if SolveSudoku(grid) {
printGrid(grid)
} else {
fmt.Println("No solution exists")
}
// 不需要显式返回,Go的main函数是"void"类型
}
func UsedInCol(grid [][]int, col int, num int) bool {
// 我们可以使用range来遍历整个切片。
// 第一个值(我们忽略它)是切片的索引,你用来称之为"row"的值。
// 现在row是包含给定行的切片,这类似于迭代器。
for _, row := range grid {
if row[col] == num {
return true
}
}
return false
}
其他注意事项:
-
你之前使用的常量
N
现在可以用len(grid)
来代替。要获取列的长度,使用len(grid[0])
(注意:确保grid[0]
存在)。 -
由于它是一个值,将
[N][N]int
作为参数或返回值几乎总是低效的,因为它在每次函数调用时都会进行一次相当大的复制。指针追踪通常更快,除非N很小(可能是1-2)。 -
在Go中不存在引用,除了极其技术性的情况,比如闭包变量的行为。
-
更好的方法可能是声明类似
type SudokuGrid struct { grid []int; rows,cols int }
这样的结构体,其中包含一些At/Set方法,可以让你恢复一些关于大小的保证。我将把这个任务(以及它是否是一个好主意)留给你。
英文:
Your issue isn't really that you don't understand pointers. Pointers mostly work the same way in Go as in C++ (except since Go has things like escape analysis and GCs you don't have to worry about dangling or invalid pointers, at the dubious cost of no pointer arithmetic).
Your issue is a misunderstanding in the difference between an array in C++ and Go. In C++ an array is literally a pointer, the syntax is an alias. Doing int a[9]
and int *a
are the same type, the static sizing and free initialization is just a cool compiler trick. Accessing an array is fancy syntax for pointer arithmetic followed by a dereference -- which, as mentioned, is something Go doesn't have.
In Go, arrays are values rather than pointers. When you have a function that takes a [9]int
you're literally telling the compiler to copy nine integer values, rather than a pointer to a location in memory that happens to have nine integer values. Think of func(a [2]int)
as being a nice way to write func(a1, a2 int)
.
This also leads to another subtle code difference, in C++ int a[9][9]
is a pointer to nine pointers to nine ints each. In Go it's a literal, contiguous RMO stored block of 9x9 integers.
There are two simple solutions in Go:
-
Use a pointer to an array. As in
grid *[N][N]int
. This works fine, but it's a bit unclean to access and store things in the array. You'll have to use(*grid)[i][j]
to explicitly dereference the pointer which looks ugly and can be a bit hard to read. -
Use slices. This is the better option, and more idiomatic Go style. It also avoids that nasty global constant being all over the place. The tradeoff is that you do sacrifice some a priori guarantees about the sizing of the columns.
I'll rewrite a couple methods with a slice with a couple comments and leave the rest to you:
func main() {
// We can omit the []int on every line, Go infers it
var grid = [][]int{
{3, 0, 6, 5, 0, 8, 4, 0, 0},
{5, 2, 0, 0, 0, 0, 0, 0, 0},
{0, 8, 7, 0, 0, 0, 0, 3, 1},
{0, 0, 3, 0, 1, 0, 0, 8, 0},
{9, 0, 0, 8, 6, 3, 0, 0, 5},
{0, 5, 0, 0, 9, 0, 6, 0, 0},
{1, 3, 0, 0, 0, 0, 2, 5, 0},
{0, 0, 0, 0, 0, 0, 0, 7, 4},
{0, 0, 5, 2, 0, 6, 3, 0, 0},
}
// == true is superfluous
if SolveSudoku(grid) {
printGrid(grid)
} else {
fmt.Println("No solution exists")
}
// Don't need to explicitly return, Go's mains are "void"
}
func UsedInCol(grid [][]int, col int, num int) bool {
// We can use range to iterate over the whole slice.
// The first value (which we ignore) is the slice index, the value you
// used to call "row".
// Now row is the slice containing the given row, this is similar to an iterator.
for _,row := range grid {
if row[col] == num {
return true
}
}
return false
}
Miscellaneous notes:
-
Where you used the constant
N
before, you can now uselen(grid)
. To get the length of a column, uselen(grid[0])
(warning: make suregrid[0]
exists). -
Due to it being a value, having a
[N][N]int
as an argument or return value is almost always inefficient because it's a rather large copy on every function call. Pointer chasing is usually faster, except for very small values of N (maybe 1-2). -
References don't exist in Go, except in extremely technical senses like the behavior of closed-over variables.
-
An even better way to do this may be to declare something like
type SudokuGrid struct { grid []int; rows,cols int }
with some sort of At/Set method set, which lets you have some sizing guarantees back. I'll leave doing this (or whether it's even a good idea) up to you.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论