使用指针将数据追加到切片[][]int时出现错误。

huangapple go评论93阅读模式
英文:

Error when using pointers to append into slice [][]int

问题

在我尝试解决LC上的一个问题"Subset II"时,我遇到了一个奇怪的问题。代码从给定的集合生成一个幂集。
然而,当我运行代码时,它失败了,因为其中一个集合不正确。

集合[0,3,5,7]被替换为[0,3,5,9](因此被添加了两次)。

我在一个集合被添加到res之前有一个打印语句(在代码中有突出显示),它打印出正确的幂集。

我唯一能想到的问题是使用指针将值追加到切片中,但由于它不是并发运行的,我不明白为什么会有竞争条件。
如果有人能指出我的错误,我将不胜感激。

package main

import (
	"fmt"
	"sort"
)

func ValueCount(nums []int) map[int]int {
	hm := make(map[int]int)
	for _, v := range nums {
		if c, ok := hm[v]; ok {
			hm[v] = c + 1
		} else {
			hm[v] = 1
		}
	}
	return hm
}

func subsetsWithDup(nums []int) [][]int {
	var res [][]int
	res = append(res, []int{})
	sort.Ints(nums)
	hashMap := ValueCount(nums)
	var t []int
	printTest(nums, t, &res, hashMap)
	return res
}

func printTest(nums []int, t []int, res *[][]int, hm map[int]int) {
	if len(nums) == 0 {
		return
	}
	for i := 0; i < len(nums); {
		v := nums[i]
		x := nums[i:]
		for k := 0; k < hm[v]; k++ {
			var a, b []int
			for z := 0; z < k+1; z++ {
				a = append(t, x[z])
			}
			fmt.Println(a) // <--------- 打印被追加到res的值
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}
		i += hm[v]
	}

}

func main() {
	n := []int{9, 0, 3, 5, 7}
	fmt.Println("Find the power set of:", n)
	fmt.Println(subsetsWithDup(n))
}

// [0,3,5,7] 在输出中变为
// [0,3,5,9]
英文:

While I was trying to solve a problem "Subset II" from LC, I came across a strange problem. The code generates a power set from a given set.
However, when I run the code it failed because one of the set wasn't correct.

The set [0,3,5,7] replaced by [0,3,5,9] (hence gets appended twice).

I have a print statement (highlighted in code) right before a set gets appended to res, and it prints the correct power set.

The only issue I could think is the use of pointers to append values into a slice, however since it's does not run concurrently I don't see why there would be a race condition.
Appreciate if someone can point out my mistake.

package main
import (
&quot;fmt&quot;
&quot;sort&quot;
)
func ValueCount( nums []int) map[int]int{
hm := make(map[int]int)
for _,v := range(nums){
if c, ok := hm[v]; ok {
hm[v] = c + 1
}else{
hm[v] = 1
}
}
return hm
}
func subsetsWithDup(nums []int) [][]int {
var res [][]int
res = append(res,[]int{})
sort.Ints(nums)
hashMap := ValueCount(nums)
var t []int
printTest(nums, t, &amp;res, hashMap)
return res
}
func printTest(nums []int, t []int, res *[][]int, hm map[int]int) {
if len(nums) == 0 {
return
}
for i:= 0; i &lt; len(nums); {
v := nums[i]
x := nums[i:]
for  k:= 0; k&lt; hm[v]; k++ {
var a,b []int
for z:= 0; z&lt;k+1; z++ { 
a = append(t,x[z])
}
fmt.Println(a) // &lt;--------- Prints the values that gets appended to res
*res = append(*res, a)	
b = a
printTest(nums[i+hm[v]:], b, res, hm)
}
i += hm[v]
}
}
func main(){
n := []int{9,0,3,5,7}
fmt.Println(&quot;Find the power set of:&quot;, n)
fmt.Println(subsetsWithDup(n))
}
// [0,3,5,7] changes to 
// [0,3,5,9] in the output

答案1

得分: 1

非常小心地使用(和重复使用)切片结果 - 尤其是在稍后修改这些切片值时。由于切片具有支持数组,所以引用的数据可能以非常意外的方式发生变化!

解决问题的快速方法是将切片结果复制到一个新的切片中。这样可以确保对原始切片的更改不会引入错误(尤其是在递归算法中)。

复制一个切片的方法如下:

func copyIntSlice(a []int) []int {
    c := make([]int, len(a))
    copy(c, a) // `a` 现在可以增长/缩小/更改,而不会影响 `c`
    return c
}

然后在你的主要代码中调用它:

aCopy := copyIntSlice(a)
*res = append(*res, aCopy)

printTest(nums[i+hm[v]:], aCopy, res, hm)

这里是一个示例链接。

英文:

Be very careful using (and reusing) slice results - especially when altering those slice values later. Since slices have backing arrays, the referenced data can change in very unexpected ways!

A quick fix to your problem is to copy slice results to a new slice. This ensures changes to the original slice do not introduce bugs (especially in a recursive algorithm).

To copy a slice:

func copyIntSlice(a []int) []int {
c := make([]int, len(a))
copy(c, a) // `a` can now grow/shrink/change without affecting `c`
return c
}

and just call this from your main code:

aCopy := copyIntSlice(a)
*res = append(*res, aCopy)
printTest(nums[i+hm[v]:], aCopy, res, hm)

https://play.golang.org/p/1p8Z4sV9foQ

答案2

得分: 1

错误发生在第40行:

a = append(t, x[z])

一个快速修复方法是更改这个for循环:

		for k := 0; k < hm[v]; k++ {
			var a, b []int
			for z := 0; z < k+1; z++ {
				a = append(t, x[z])
			}
			fmt.Println(a) // <--------- 打印附加到res的值
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}

将其更改为:

		for k := 0; k < hm[v]; k++ {
			var a, b []int
			a = make([]int, len(t))
			copy(a, t)
			for z := 0; z < k+1; z++ {
				a = append(a, x[z])
			}
			fmt.Println(a) // <--------- 打印附加到res的值
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}

这与Go如何使用切片作为数据结构有关。当内置的append函数的第一个参数是切片参数时,它会复制一些对程序员来说不直观的切片的内部数据。然后修改了参数切片t和新创建的切片a

如果你有兴趣了解更多信息,我建议阅读slice internals

完整的程序已经修改如下:

package main

import (
	"fmt"
	"sort"
)

func ValueCount(nums []int) map[int]int {
	hm := make(map[int]int)
	for _, v := range nums {
		if c, ok := hm[v]; ok {
			hm[v] = c + 1
		} else {
			hm[v] = 1
		}
	}
	return hm
}

func subsetsWithDup(nums []int) [][]int {
	var res [][]int
	res = append(res, []int{})
	sort.Ints(nums)
	hashMap := ValueCount(nums)
	var t []int
	printTest(nums, t, &res, hashMap)
	return res
}

func printTest(nums []int, t []int, res *[][]int, hm map[int]int) {
	if len(nums) == 0 {
		return
	}
	for i := 0; i < len(nums); {
		v := nums[i]
		x := nums[i:]
		for k := 0; k < hm[v]; k++ {
			var a, b []int
			a = make([]int, len(t))
			copy(a, t)
			for z := 0; z < k+1; z++ {
				a = append(a, x[z])
			}
			fmt.Println(a) // <--------- 打印附加到res的值
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}
		i += hm[v]
	}

}

func main() {
	n := []int{9, 0, 3, 5, 7}
	fmt.Println("Find the power set of:", n)
	fmt.Println(subsetsWithDup(n))
}

新的输出结果:

Find the power set of: [9 0 3 5 7]
[0]
[0 3]
[0 3 5]
[0 3 5 7]
[0 3 5 7 9]
[0 3 5 9]
[0 3 7]
[0 3 7 9]
[0 3 9]
[0 5]
[0 5 7]
[0 5 7 9]
[0 5 9]
[0 7]
[0 7 9]
[0 9]
[3]
[3 5]
[3 5 7]
[3 5 7 9]
[3 5 9]
[3 7]
[3 7 9]
[3 9]
[5]
[5 7]
[5 7 9]
[5 9]
[7]
[7 9]
[9]
[[] [0] [0 3] [0 3 5] [0 3 5 7] [0 3 5 7 9] [0 3 5 9] [0 3 7] [0 3 7 9] [0 3 9] [0 5] [0 5 7] [0 5 7 9] [0 5 9] [0 7] [0 7 9] [0 9] [3] [3 5] [3 5 7] [3 5 7 9] [3 5 9] [3 7] [3 7 9] [3 9] [5] [5 7] [5 7 9] [5 9] [7] [7 9] [9]]
英文:

The bug occurs on line 40:

a = append(t, x[z])

A quick fix would be to change this for loop:

		for k := 0; k &lt; hm[v]; k++ {
			var a, b []int
			for z := 0; z &lt; k+1; z++ {
				a = append(t, x[z])
			}
			fmt.Println(a) // &lt;--------- Prints the values that gets appended to res
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}

To this:

		for k := 0; k &lt; hm[v]; k++ {
			var a, b []int
			a = make([]int, len(t))
			copy(a, t)
			for z := 0; z &lt; k+1; z++ {
				a = append(a, x[z])
			}
			fmt.Println(a) // &lt;--------- Prints the values that gets appended to res
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}

It has to do with how Go uses slices as a data structure. When the first argument to the built-in append function was a slice argument, it copied some of the slice's internal data that wasn't intuitive to the programmer. It then modified the argument slice, t, and the newly created slice, a.

I'd recommend reading up on slice internals if you're interested in learning more.

Full program edited:

package main

import (
	&quot;fmt&quot;
	&quot;sort&quot;
)

func ValueCount(nums []int) map[int]int {
	hm := make(map[int]int)
	for _, v := range nums {
		if c, ok := hm[v]; ok {
			hm[v] = c + 1
		} else {
			hm[v] = 1
		}
	}
	return hm
}

func subsetsWithDup(nums []int) [][]int {
	var res [][]int
	res = append(res, []int{})
	sort.Ints(nums)
	hashMap := ValueCount(nums)
	var t []int
	printTest(nums, t, &amp;res, hashMap)
	return res
}

func printTest(nums []int, t []int, res *[][]int, hm map[int]int) {
	if len(nums) == 0 {
		return
	}
	for i := 0; i &lt; len(nums); {
		v := nums[i]
		x := nums[i:]
		for k := 0; k &lt; hm[v]; k++ {
			var a, b []int
			a = make([]int, len(t))
			copy(a, t)
			for z := 0; z &lt; k+1; z++ {
				a = append(a, x[z])
			}
			fmt.Println(a) // &lt;--------- Prints the values that gets appended to res
			*res = append(*res, a)
			b = a
			printTest(nums[i+hm[v]:], b, res, hm)
		}
		i += hm[v]
	}

}

func main() {
	n := []int{9, 0, 3, 5, 7}
	fmt.Println(&quot;Find the power set of:&quot;, n)
	fmt.Println(subsetsWithDup(n))
}

New output:

Find the power set of: [9 0 3 5 7]
[0]
[0 3]
[0 3 5]
[0 3 5 7]
[0 3 5 7 9]
[0 3 5 9]
[0 3 7]
[0 3 7 9]
[0 3 9]
[0 5]
[0 5 7]
[0 5 7 9]
[0 5 9]
[0 7]
[0 7 9]
[0 9]
[3]
[3 5]
[3 5 7]
[3 5 7 9]
[3 5 9]
[3 7]
[3 7 9]
[3 9]
[5]
[5 7]
[5 7 9]
[5 9]
[7]
[7 9]
[9]
[[] [0] [0 3] [0 3 5] [0 3 5 7] [0 3 5 7 9] [0 3 5 9] [0 3 7] [0 3 7 9] [0 3 9] [0 5] [0 5 7] [0 5 7 9] [0 5 9] [0 7] [0 7 9] [0 9] [3] [3 5] [3 5 7] [3 5 7 9] [3 5 9] [3 7] [3 7 9] [3 9] [5] [5 7] [5 7 9] [5 9] [7] [7 9] [9]]

huangapple
  • 本文由 发表于 2021年8月23日 07:56:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/68885997.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定