在不影响原始的情况下克隆 Go 中的浮动切片

Clone float slice in Go without affecting the original

我想在 Go 中克隆一个 [][]float64 slice 而不影响原始数组。我该怎么做?

在下面的示例中,我希望切片 a1a2 更改时不受影响。目前,我正在使用 Go 内置的附加功能。但我一直无法获得所需的功能。

package main

import (
    "fmt"
)

func main() {
    a1 := [][]float64{[]float64{1.0,1.0}, []float64{2.0,2.1}}   
    a2 := make([][]float64, len(a1))
    
    a2 = append([][]float64{}, a1...)
    fmt.Println(a2, a1) // At this step, a1 has not changed.
    
    a2[1][0] = 5.0 //Change value of one element of a2.
    
    fmt.Println(a2, a1) // At this step, a1 has changed.
}

>> [[1 1] [2 2.1]] [[1 1] [2 2.1]]
>> [[1 1] [5 2.1]] [[1 1] [5 2.1]]

当我使用Go的复制功能时,我发现它支持int数据类型。使用复制功能时出现以下错误。可以理解,以下错误是由于 Go 中的副本预期类型不匹配。

cannot use copy(a2, a1) (type int) as type [][]float64 in assignment

我想使用切片而不是数组。

我正在使用 this reference。我是 Go 的新手,非常感谢任何帮助。

多维切片是切片的切片。如果使用循环的 2D 切片,则必须单独克隆每个切片。

像这样:

package main

import (
    "fmt"
)

func Clone(arr [][]float64) (res [][]float64) {
    res = make([][]float64, len(arr))
    for i := range arr {
        res[i] = append([]float64{}, arr[i]...)
    }
    return
}

func main() {
    a1 := [][]float64{{1.0, 1.0}, {2.0, 2.1}}
    a2 := Clone(a1)

    fmt.Println(a2, a1) // At this step, a1 has not changed.

    a2[1][0] = 5.0 //Change value of one element of a2.

    fmt.Println(a2, a1)
}

版画

[[1 1] [2 2.1]] [[1 1] [2 2.1]]
[[1 1] [5 2.1]] [[1 1] [2 2.1]]

复制切片的元素有效,但复制切片无效,因为切片本身不包含元素。它充当对底层数组的引用,该数组在操作切片时不会被复制。即使在切片上使用 copy 命令也不会导致底层数组重复。

这在围棋之旅中有描述:https://tour.golang.org/moretypes/8

Slices are like references to arrays

A slice does not store any data, it just describes a section of an underlying array.

Changing the elements of a slice modifies the corresponding elements of its underlying array.

Other slices that share the same underlying array will see those changes.

您可以使用range方法迭代您的二维切片,在循环内创建一个与您的内部切片长度相同的临时切片并将其分配给目标切片索引,之后您可以使用copy method.Here 是具有相同逻辑的代码:

package main

import (
    "fmt"
)

func main() {
    a1 := [][]float64{[]float64{1.0, 1.0}, []float64{2.0, 2.1}}

    a3 := make([][]float64, len(a1))

    copySlice(a3, a1)

    fmt.Println(a3, a1)
    a3[1][0] = 10.0
    fmt.Println(a3, a1)
}

func copySlice(dest [][]float64, src [][]float64) {

    for i := range src {

        tmp := make([]float64, len(src[i]))

        dest[i] = tmp
        copy(dest[i], src[i])

    }

    return

}

输出:

[[1 1] [2 2.1]] [[1 1] [2 2.1]]
[[1 1] [10 2.1]] [[1 1] [2 2.1]]

提供的答案是正确的,但是他们在循环中分配内存,并且内存分配很昂贵。

存在更优化的方式。通常,您可以通过对 make 的 2 次调用来创建和填充 2D 切片:1 个小的用于保存切片 headers,1 个大的用于连续保存所有值。诀窍是每个结果切片都是大缓冲区的 ... 切片

func AllocAndCopy(arr [][]float64) (res [][]float64) {
    // 1 alloc of slice of slices, for the result
    res = make([][]float64, len(arr))

    // 1 alloc of a large slice, for the contents
    size := 0
    for _, x := range arr {
        size += len(x)
    }
    mem := make([]float64, size)

    // No more alloc during the copying
    for i, x := range arr {
        res[i] = mem[:len(x):len(x)]
        mem = mem[len(x):]
        copy(res[i], x)
    }
    return
}

在样本值非常小的 this benchmark 中,AllocAndCopyClonecopySlice 的大约 2 倍。

如果所有切片都具有完全相同的长度,而不是每个切片的任意长度,则可以用更少的代码行编写 AllocAndCopy