Perché non posso duplicare uno slice con `copy ()`?


122

Devo fare una copia di una slice in Go e leggendo i documenti c'è una funzione di copia a mia disposizione.

La funzione integrata di copia copia gli elementi da una sezione di origine in una sezione di destinazione. (In un caso speciale, copierà anche i byte da una stringa a una porzione di byte.) L'origine e la destinazione potrebbero sovrapporsi. Copia restituisce il numero di elementi copiati, che sarà il minimo di len (src) e len (dst).

Ma quando lo faccio:

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

Il mio tmpè vuoto come prima (ho anche provato a usare arr, tmp):

[]
[1 2 3]

Puoi controllarlo su go playground . Allora perché non riesco a copiare una fetta?


grazie a tutti, è davvero triste non aver notato che le fette dovrebbero avere la stessa lunghezza.
Salvador Dali

1
Non necessariamente lo stesso, ma dstdovrebbe essere grande almeno quanto il numero di elementi che vuoi copiare (per una copia completa srcsignifica len(dst) >= len(src)).
icza

2
b := append([]int{}, a...)
rocketspacer

Risposte:


210

Il builtin copy(dst, src)copia gli min(len(dst), len(src))elementi.

Quindi, se il tuo dstè vuoto ( len(dst) == 0), non verrà copiato nulla.

Prova tmp := make([]int, len(arr))( Go Playground ):

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

Uscita (come previsto):

[1 2 3]
[1 2 3]

Sfortunatamente questo non è documentato nel builtinpacchetto, ma è documentato nella specifica della lingua Go: Aggiunta e copia di sezioni :

Il numero di elementi copiati è il minimo di len(src)e len(dst).

Modificare:

Infine la documentazione di copy()è stata aggiornata e ora contiene il fatto che verranno copiate le lunghezze minime di origine e destinazione:

Copia restituisce il numero di elementi copiati, che sarà il minimo di len (src) e len (dst).


2
Per riassumere, copynon contiene logica per aumentare la fetta di destinazione se la fetta di destinazione è troppo piccola, ma c'è un'altra funzione incorporata che lo fa: append Mentre in questo esempio è meglio allocare la fetta di giusta dimensione in primo luogo, appendpuò essere utilizzato quando hai già una fetta e vuoi ingrandirla aggiungendo elementi alla fine.
thomasrutter

1
Ma perché devo creare una sezione di dimensioni limitate quando copio una sezione di dimensioni illimitate?
Alex

24

Un altro modo semplice per farlo è quello di utilizzare appendquale allocherà la fetta nel processo.

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

Uscita (come previsto):

[1 2 3]
[1 2 3]

Quindi una scorciatoia per copiare array arrsarebbeappend([]int(nil), arr...)

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


8
il problema qui è che negli esempi del mondo reale, che sono molto più grandi, append allocerà la memoria in eccesso - a meno che questo array non venga successivamente riempito fino alla capacità da qualche ulteriore elaborazione - perché è progettato per una riallocazione efficiente su chiamate ripetute. play.golang.org/p/5_6618xnXn osserva che il cap (x) aumenta a 12, non 10. ora guarda cosa succede quando 1 valore viene aggiunto ai valori 1048576 play.golang.org/p/nz32JPehhl la capacità salta di 2048 slot a 1050624, per accogliere un solo valore aggiuntivo.
j. andrew shusta

12

Se le tue fette fossero della stessa dimensione, funzionerebbe :

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

Darebbe:

3
[1 2 3]
[1 2 3]

Da " Go Slices: usage and internals ":

La funzione di copia supporta la copia tra sezioni di diverse lunghezze ( copierà solo fino al numero minore di elementi )

Il solito esempio è:

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

10

Copy () viene eseguito per la lunghezza minima di dst e src, quindi è necessario inizializzare dst alla lunghezza desiderata.

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

Produzione:

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

È possibile inizializzare e copiare tutti gli elementi in una riga utilizzando append () su uno slice nullo.

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

Esempio:

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

Produzione:

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

Confrontando con allocazione + copy (), per più di 1.000 elementi, utilizzare append. In realtà sotto 1.000 la differenza può essere trascurata, rendila una regola pratica a meno che tu non abbia molte fette.

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

1
append dovrebbe essere utilizzato nei casi in cui l'array verrà aumentato da chiamate ripetute, poiché allocherà in modo ottimistico la capacità in eccesso in previsione di ciò. copy dovrebbe essere usato una volta per array di input nei casi in cui l'array di risultati dovrebbe essere creato con le dimensioni esatte e non viene riallocato di nuovo. play.golang.org/p/0kviwKmGzx non hai condiviso il codice benchmark che ha prodotto quei risultati, quindi non posso confermare o negare la sua validità, ma trascura questo aspetto più importante.
j. andrew shusta

1
Intendi "slice" non array . Sono cose diverse.
Inanc Gumus

2

La specifica del linguaggio di programmazione Go

Aggiunta e copia di sezioni

La funzione copy copia gli elementi slice da un src sorgente a un dst di destinazione e restituisce il numero di elementi copiati. Entrambi gli argomenti devono avere lo stesso tipo di elemento T e devono essere assegnabili a uno slice di tipo [] T. Il numero di elementi copiati è il minimo di len (src) e len (dst). Come caso speciale, copy accetta anche un argomento di destinazione assegnabile al tipo [] byte con un argomento di origine di un tipo stringa. Questo modulo copia i byte dalla stringa nella fetta di byte.

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

tmpha bisogno di spazio sufficiente per arr. Per esempio,

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

Produzione:

[1 2 3]
[1 2 3]

0

NOTA: questa è una soluzione errata come dimostrato da @benlemasurier

Ecco un modo per copiare una fetta. Sono un po 'in ritardo, ma c'è una risposta più semplice e veloce di quella di @ Dave. Queste sono le istruzioni generate da un codice come quello di @ Dave. Queste sono le istruzioni generate dal mio. Come puoi vedere ci sono molte meno istruzioni. Quello che fa è solo append(slice), che copia la fetta. Questo codice:

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

Emette questo:

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]

1
Questo non è corretto, come mostrato qui: play.golang.org/p/q3CoEoaid6d . L'output previsto dovrebbe riflettere quello della risposta di @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier

1
@benlemasurier - Eh ... Sembra che tu abbia ragione! Grazie per avermi fatto sapere!
xilpex
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.