Clone float slice in Go without affecting the original

Viewed 448

I want to clone a [][]float64 slice in Go without affecting the original array. How can I do it?

In the below example, I want the slice a1 to remain unaffected when a2 is changed. At present, I am using the built in append function of Go. But I have not been able to get the desired functionality.

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]]

When I use copy function of Go, I find that it supports int datatype. I get the following error when I use the copy function. Understandably, the below error is because of Type mismatch between what copy expects in Go.

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

I want to use slices and not arrays.

I am using this reference. I am new to Go and will appreciate any help.

4 Answers

A multi-dimensional slice is a slice of slices. You have to clone each slice individually, in case of a 2D slice using a loop.

Something like this:

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)
}

Prints

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

You can use the range method to iterate over your 2D slice,inside the loop create a temporary slice of the same length as your inner slice and assign it to destination slice index,after that you can use the copy method.Here is the code with same logic:

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

}

Output:

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

The provided answers are correct, however they allocate memory in a loop, and memory allocation is expensive.

There exists a more optimized way. In general you can create and fill a 2D slice with exactly 2 calls to make: 1 small to hold slice headers, and 1 large to hold all the values contiguously. The trick is that each result slice is a ... slice of the large buffer.

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
}

In this benchmark with very small sample values, AllocAndCopy is ~2x as fast as Clone and copySlice.

It is possible to write AllocAndCopy with fewer lines of code if all the slices have the exact same length, instead of an arbitrary length for each.

Copying the elements of slice works, but copying a slice does not because a slice does not contain the elements itself. It acts as references to an underlying array which is not duplicated when manipulating slices. Even using the copy command on a slice won't result in a duplicate of the underlying array.

This is described in A Tour of Go: 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.

Related