¿Por qué no puedo duplicar un segmento con `copy ()`?

122

Necesito hacer una copia de un segmento en Go y, al leer los documentos, tengo una función de copia a mi disposición.

La función de copia incorporada copia elementos de un segmento de origen en un segmento de destino. (Como caso especial, también copiará bytes de una cadena a un segmento de bytes). El origen y el destino pueden superponerse. Copiar devuelve el número de elementos copiados, que será el mínimo de len (src) y len (dst).

Pero cuando lo hago:

arr := []int{1, 2, 3}
tmp := []int{}
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

Mi tmpestá vacío como estaba antes (incluso intenté usarlo arr, tmp):

[]
[1 2 3]

Puedes comprobarlo en el patio de juegos . Entonces, ¿por qué no puedo copiar una porción?

Salvador Dalí
fuente
gracias a todos, es realmente triste que no haya notado que las rodajas deben tener la misma longitud.
Salvador Dali
1
No necesariamente el mismo, pero dstdebería ser al menos tan grande como muchos de los elementos que desea copiar (para una copia completa srcque significa len(dst) >= len(src)).
icza
2
b := append([]int{}, a...)
rocketspacer

Respuestas:

210

Los elementos de copy(dst, src)copia incorporados min(len(dst), len(src)).

Entonces, si tu dstestá vacío ( len(dst) == 0), no se copiará nada.

Prueba tmp := make([]int, len(arr))( Ir al patio de recreo ):

arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

Salida (como se esperaba):

[1 2 3]
[1 2 3]

Desafortunadamente, esto no está documentado en el builtinpaquete, pero está documentado en la Especificación de idioma de Go: Agregar y copiar porciones :

El número de elementos copiados es el mínimo de len(src)y len(dst).

Editar:

Finalmente copy()se ha actualizado la documentación de y ahora contiene el hecho de que se copiará la longitud mínima de origen y destino:

Copiar devuelve el número de elementos copiados, que será el mínimo de len (src) y len (dst).

icza
fuente
2
Para resumir, copyno contiene lógica para hacer crecer el segmento de destino si el segmento de destino es demasiado pequeño, pero hay otra función incorporada que sí lo hace: append mientras que en este ejemplo es mejor asignar el segmento del tamaño correcto en primer lugar, appendse puede usar cuando ya tiene un segmento y desea aumentarlo agregando elementos al final.
thomasrutter
1
Pero, ¿por qué tengo que crear un segmento de tamaño limitado al copiar un segmento de tamaño ilimitado?
Alex
24

Otra forma sencilla de hacer esto es usando appendwhich asignará el segmento en el proceso.

arr := []int{1, 2, 3}
tmp := append([]int(nil), arr...)  // Notice the ... splat
fmt.Println(tmp)
fmt.Println(arr)

Salida (como se esperaba):

[1 2 3]
[1 2 3]

Entonces, una forma abreviada de copiar matriz arrseríaappend([]int(nil), arr...)

https://play.golang.org/p/sr_4ofs5GW

Dave
fuente
8
el problema aquí es que en los ejemplos del mundo real, que son mucho más grandes, append asignará el exceso de memoria, a menos que esta matriz se llene más tarde a su capacidad mediante algún procesamiento adicional, porque está diseñada para una reasignación eficiente en llamadas repetidas. play.golang.org/p/5_6618xnXn observe que el límite (x) aumenta a 12, no a 10. ahora mire lo que sucede cuando se agrega 1 valor a 1048576 valores play.golang.org/p/nz32JPehhl la capacidad salta en 2048 ranuras para 1050624, para acomodar solo un valor adicional.
j. Andrew Shusta
12

Si sus rebanadas fueran del mismo tamaño, funcionaría :

arr := []int{1, 2, 3}
tmp := []int{0, 0, 0}
i := copy(tmp, arr)
fmt.Println(i)
fmt.Println(tmp)
fmt.Println(arr)

Daría:

3
[1 2 3]
[1 2 3]

De " Go Slices: uso e internos ":

La función de copia admite la copia entre cortes de diferentes longitudes ( solo copiará hasta el número más pequeño de elementos )

El ejemplo habitual es:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
VonC
fuente
10

El copy () se ejecuta para la menor longitud de dst y src, por lo que debe inicializar el dst a la longitud deseada.

A := []int{1, 2, 3}
B := make([]int, 3)
copy(B, A)
C := make([]int, 2)
copy(C, A)
fmt.Println(A, B, C)

Salida:

[1 2 3] [1 2 3] [1 2]

Puede inicializar y copiar todos los elementos en una línea usando append () en un segmento nulo.

x := append([]T{}, []...)

Ejemplo:

A := []int{1, 2, 3}
B := append([]int{}, A...)
C := append([]int{}, A[:2]...)
fmt.Println(A, B, C)    

Salida:

[1 2 3] [1 2 3] [1 2]

En comparación con la asignación + copia (), para más de 1000 elementos, use append. En realidad, por debajo de 1,000, la diferencia puede pasarse por alto, conviértalo en una regla general a menos que tenga muchas rebanadas.

BenchmarkCopy1-4                50000000            27.0 ns/op
BenchmarkCopy10-4               30000000            53.3 ns/op
BenchmarkCopy100-4              10000000           229 ns/op
BenchmarkCopy1000-4              1000000          1942 ns/op
BenchmarkCopy10000-4              100000         18009 ns/op
BenchmarkCopy100000-4              10000        220113 ns/op
BenchmarkCopy1000000-4              1000       2028157 ns/op
BenchmarkCopy10000000-4              100      15323924 ns/op
BenchmarkCopy100000000-4               1    1200488116 ns/op
BenchmarkAppend1-4              50000000            34.2 ns/op
BenchmarkAppend10-4             20000000            60.0 ns/op
BenchmarkAppend100-4             5000000           240 ns/op
BenchmarkAppend1000-4            1000000          1832 ns/op
BenchmarkAppend10000-4            100000         13378 ns/op
BenchmarkAppend100000-4            10000        142397 ns/op
BenchmarkAppend1000000-4            2000       1053891 ns/op
BenchmarkAppend10000000-4            200       9500541 ns/op
BenchmarkAppend100000000-4            20     176361861 ns/op
Esze
fuente
1
append debe usarse en los casos en que la matriz se incrementará por llamadas repetidas, ya que asignará de manera optimista el exceso de capacidad en previsión de esto. La copia debe usarse una vez por matriz de entrada en los casos en que la matriz de resultados se debe crear con el tamaño exacto y no se reasigna de nuevo. play.golang.org/p/0kviwKmGzx no compartiste el código de referencia que produjo esos resultados, así que no puedo confirmar ni negar su validez, pero pasa por alto este aspecto más importante.
j. Andrew Shusta
1
Quieres decir 'rebanada', no matriz . Son cosas diferentes.
Inanc Gumus
2

La especificación del lenguaje de programación Go

Agregar y copiar sectores

La función copy copia elementos de segmento de un origen src a un destino dst y devuelve el número de elementos copiados. Ambos argumentos deben tener el mismo tipo de elemento T y deben ser asignables a un segmento de tipo [] T. El número de elementos copiados es el mínimo de len (src) y len (dst). Como caso especial, la copia también acepta un argumento de destino asignable al byte de tipo [] con un argumento de origen de un tipo de cadena. Este formulario copia los bytes de la cadena en el segmento de bytes.

copy(dst, src []T) int
copy(dst []byte, src string) int

tmpnecesita suficiente espacio para arr. Por ejemplo,

package main

import "fmt"

func main() {
    arr := []int{1, 2, 3}
    tmp := make([]int, len(arr))
    copy(tmp, arr)
    fmt.Println(tmp)
    fmt.Println(arr)
}

Salida:

[1 2 3]
[1 2 3]
peterSO
fuente
0

NOTA: Esta es una solución incorrecta como demostró @benlemasurier

A continuación, se muestra una forma de copiar un sector. Llego un poco tarde, pero hay una respuesta más simple y rápida que la de @ Dave. Estas son las instrucciones generadas a partir de un código como el de @ Dave. Estas son las instrucciones generadas por la mía. Como puede ver, hay muchas menos instrucciones. Lo que hace es simplemente lo que hace append(slice), que copia el corte. Este código:

package main

import "fmt"

func main() {
    var foo = []int{1, 2, 3, 4, 5}
    fmt.Println("foo:", foo)
    var bar = append(foo)
    fmt.Println("bar:", bar)
    bar = append(bar, 6)
    fmt.Println("foo after:", foo)
    fmt.Println("bar after:", bar)
}

Produce esto:

foo: [1 2 3 4 5]
bar: [1 2 3 4 5]
foo after: [1 2 3 4 5]
bar after: [1 2 3 4 5 6]
xilpex
fuente
1
Esto es incorrecto, como se muestra aquí: play.golang.org/p/q3CoEoaid6d . El resultado esperado debería reflejar el de la respuesta de @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier hace
1
@benlemasurier - Eh ... ¡Parece que tienes razón! ¡Gracias por hacérmelo saber!
xilpex hace