去切片表现不如预期

时间:2019-12-10 12:24:35

标签: go

我正在尝试动手实践,发现它的行为不符合预期。

我试图获取diff大小的n个数组的笛卡尔积。输出不正确。

这是我的代码


func main() {
   sl1 := []int{1,2,3}
   sl2 := []int{4}
   sl3 := []int{5,6}
   sl4 := []int{8,9}
   sl := [][]int{sl1,sl2,sl3,sl4}
   res := cartesianMain(sl)
   fmt.Println(res)
}

func cartesianMain(a [][]int)  [][]int {
   res := [][]int{}
   for i:=0;i<len(a[0]) ;i++{
       res = append(res,[]int{a[0][i]})
   }
   for i:= 1;i<len(a) ;i++{
       res = cartesianProduct(res,a[i])
   }
   return res;
}
func cartesianProduct(a [][]int, b []int) [][]int {
   result := [][]int{}
   for _,v1 := range b {
       for _,v2 := range a {
           result = append(result, append(v2,v1))
       }
   }
return result
}

实际输出:

[[1 4 5 9] [2 4 5 9] [3 4 5 9] [1 4 6 9] [2 4 6 9] [3 4 6 9] [1 4 5 9] [2 4 5 9] [3 4 5 9] [1 4 6 9] [2 4 6 9] [3 4 6 9]]

预期输出: 如果看到sl4的第一个元素8被9覆盖。 正确答案将是:

[[1 4 5 8] [2 4 5 8] [3 4 5 8] [1 4 6 8] [2 4 6 8] [3 4 6 8] [1 4 5 9] [2 4 5 9] [3 4 5 9] [1 4 6 9] [2 4 6 9] [3 4 6 9]]

1 个答案:

答案 0 :(得分:2)

这是因为append的工作方式。在Go中,slice是具有3个属性的标头:LenCapPtrLen是它本身的切片的长度,Cap是切片的未扩展数组(内存)的容量,Ptr是指向该数组的指针。

append追加到基础数组中没有更多空间的切片时,它会分配一个新的基础数组,该数组的空间比需要的多,并将原始切片的内容复制到其中,然后添加新元素。

然后,当append追加到Cap> Len的切片时,即在已分配的内存中仍然有足够的空间时,append会保留基础数组和复制要添加到a[Len+1]的元素(其中a是基础数组)。当两个或多个片共享基础内存时,这将导致问题。

经验法则是经常检查复制切片的必要性,以避免不必要的底层数组共享。

要解决此问题,请将result = append(result, append(v2,v1))更改为result = append(result, append([]int{}, append(v2, v1)...))

另请参阅:https://blog.golang.org/slices

注1:append([]int{},append(v2,v1...))copyapply的快捷方式。有关切片的更多技巧,请访问:https://github.com/golang/go/wiki/SliceTricks

注2:在Go中,nil是slice的有效值。因此您可以通过将cartesianMain设置为res来消除[]int{nil}中分割的分隔。

注3:为了获得更好的性能和更少的分配,最好为已知片设置容量(或长度)。在cartesianProduct中,您可以使用result := make([][]int, 0, len(a)*len(b))

游乐场:https://play.golang.org/p/rLqDGWoTLKS