Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clone float slice in Go without affecting the original

Tags:

arrays

slice

go

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.

like image 789
Ling Guo Avatar asked Jul 27 '21 09:07

Ling Guo


People also ask

How do you duplicate a slice in Golang?

To duplicate a slice in Go, getting a deep copy of its contents, you need to either use the built-in copy() function, or create a new empty slice and add all the elements of the first slice to it using the append() function.

Is slice dynamic in Golang?

But arrays in Golang cannot be resized, hence we have slices, whose size can be dynamically changed. Slices have length and capacity. Length is the number of elements present in the slice.

How do you clear a slice in Golang?

Setting the slice to nil is the best way to clear a slice. nil slices in go are perfectly well behaved and setting the slice to nil will release the underlying memory to the garbage collector.

What is the difference between Slice and array in Golang?

Slices in Go and Golang The basic difference between a slice and an array is that a slice is a reference to a contiguous segment of an array. Unlike an array, which is a value-type, slice is a reference type. A slice can be a complete array or a part of an array, indicated by the start and end index.


Video Answer


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]]
like image 68
rustyx Avatar answered Oct 17 '22 19:10

rustyx


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]]
like image 32
Gopher Avatar answered Oct 17 '22 18:10

Gopher


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.

like image 2
Deleplace Avatar answered Oct 17 '22 18:10

Deleplace


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.

like image 1
Grokify Avatar answered Oct 17 '22 18:10

Grokify