Pourquoi ne puis-je pas dupliquer une tranche avec `copy ()`?


122

J'ai besoin de faire une copie d'une tranche dans Go et en lisant la documentation, une fonction de copie est à ma disposition.

La fonction intégrée de copie copie les éléments d'une tranche source dans une tranche de destination. (Dans un cas particulier, il copiera également les octets d'une chaîne vers une tranche d'octets.) La source et la destination peuvent se chevaucher. Copy renvoie le nombre d'éléments copiés, qui sera le minimum de len (src) et len ​​(dst).

Mais quand je fais:

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

My tmpest vide comme avant (j'ai même essayé de l'utiliser arr, tmp):

[]
[1 2 3]

Vous pouvez le vérifier sur le terrain de jeu . Alors pourquoi ne puis-je pas copier une tranche?


merci à tous, c'est vraiment triste de ne pas avoir remarqué que les tranches doivent avoir la même longueur.
Salvador Dali

1
Pas nécessairement la même chose, mais dstdevrait être au moins aussi grande que le nombre d'éléments que vous souhaitez copier (pour une copie complète de srccela signifie len(dst) >= len(src)).
icza

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

Réponses:


210

Les BUILTIN copy(dst, src)copies des min(len(dst), len(src))éléments.

Donc, si votre dstest vide ( len(dst) == 0), rien ne sera copié.

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

Sortie (comme prévu):

[1 2 3]
[1 2 3]

Malheureusement, cela n'est pas documenté dans le builtinpackage, mais il est documenté dans la spécification du langage Go: Ajout et copie de tranches :

Le nombre d'éléments copiés est le minimum de len(src)et len(dst).

Éditer:

Enfin, la documentation de copy()a été mise à jour et contient désormais le fait que la longueur minimale de la source et de la destination sera copiée:

Copy renvoie le nombre d'éléments copiés, qui sera le minimum de len (src) et len ​​(dst).


2
Pour résumer, copyne contient pas de logique pour augmenter la tranche de destination si la tranche de destination est trop petite, mais il existe une autre fonction intégrée qui le fait: alors append que dans cet exemple, il est préférable d'allouer la tranche de bonne taille en premier lieu, appendpeut être utilisé lorsque vous avez déjà une tranche et que vous souhaitez l'agrandir en ajoutant des éléments à la fin.
thomasrutter

1
Mais pourquoi dois-je créer une tranche de taille limitée lors de la copie d'une tranche de taille illimitée?
Alex

24

Un autre moyen simple de le faire est d'utiliser appendqui attribuera la tranche dans le processus.

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

Sortie (comme prévu):

[1 2 3]
[1 2 3]

Donc, un raccourci pour copier un tableau arrseraitappend([]int(nil), arr...)

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


8
le hic ici est que dans les exemples du monde réel, qui sont beaucoup plus volumineux, append allouera un excès de mémoire - à moins que ce tableau ne soit plus tard rempli à pleine capacité par un traitement supplémentaire - car il est conçu pour une réallocation efficace sur des appels répétés. play.golang.org/p/5_6618xnXn observez que cap (x) augmente à 12, et non à 10. maintenant regardez ce qui se passe quand 1 valeur est ajoutée à 1048576 valeurs play.golang.org/p/nz32JPehhl la capacité augmente de 2048 slots pour 1050624, pour n'accepter qu'une seule valeur supplémentaire.
j. andrew shusta

12

Si vos tranches étaient de la même taille, cela fonctionnerait :

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

Donnerait:

3
[1 2 3]
[1 2 3]

À partir de " Go Slices: utilisation et éléments internes ":

La fonction de copie prend en charge la copie entre des tranches de différentes longueurs ( elle ne copiera que jusqu'au plus petit nombre d'éléments )

L'exemple habituel est:

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

10

Le copy () s'exécute sur la plus petite longueur de dst et src, vous devez donc initialiser le dst à la longueur souhaitée.

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

Production:

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

Vous pouvez initialiser et copier tous les éléments sur une seule ligne en utilisant append () sur une tranche nil.

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

Exemple:

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

Production:

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

En comparaison avec allocation + copy (), pour plus de 1 000 éléments, utilisez append. En fait, en dessous de 1000, la différence peut être négligée, faites-en une règle empirique à moins que vous n'ayez plusieurs tranches.

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 doit être utilisé dans les cas où le tableau sera augmenté par des appels répétés, car il allouera de manière optimiste la capacité excédentaire en prévision de cela. la copie doit être utilisée une fois par tableau d'entrée dans les cas où le tableau de résultats doit être créé à la taille exacte et n'est pas réalloué à partir de nouveau. play.golang.org/p/0kviwKmGzx vous n'avez pas partagé le code de référence qui a produit ces résultats, donc je ne peux pas confirmer ou nier sa validité, mais il néglige cet aspect plus important.
j. andrew shusta

1
Vous voulez dire «tranche» et non tableau . Ce sont des choses différentes.
Inanc Gumus

2

Spécification du langage de programmation Go

Ajout et copie de tranches

La fonction copy copie les éléments de tranche d'un src source vers un dst de destination et renvoie le nombre d'éléments copiés. Les deux arguments doivent avoir le même type d'élément T et doivent être assignables à une tranche de type [] T. Le nombre d'éléments copiés est le minimum de len (src) et len ​​(dst). En tant que cas particulier, copy accepte également un argument de destination assignable à l'octet de type [] avec un argument source de type chaîne. Ce formulaire copie les octets de la chaîne dans la tranche d'octets.

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

tmpa besoin de suffisamment de place pour arr. Par exemple,

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

Production:

[1 2 3]
[1 2 3]

0

REMARQUE: c'est une solution incorrecte comme l'a prouvé @benlemasurier

Voici un moyen de copier une tranche. Je suis un peu en retard, mais il y a une réponse plus simple et plus rapide que celle de @ Dave. Ce sont les instructions générées à partir d'un code comme celui de @ Dave. Ce sont les instructions générées par le mien. Comme vous pouvez le voir, il y a beaucoup moins d'instructions. Ce qu'il fait, c'est qu'il fait juste append(slice), qui copie la tranche. Ce code:

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

Sort ceci:

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
Ceci est incorrect, comme indiqué ici: play.golang.org/p/q3CoEoaid6d . Le résultat attendu doit refléter celui de la réponse de @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier il y a

1
@benlemasurier - Huh ... Vous avez raison! Merci de me le faire savoir!
xilpex il y a
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.