Por que não consigo duplicar uma fatia com `copy ()`?

121

Preciso fazer uma cópia de uma fatia no Go e, lendo os documentos, há uma função de cópia à minha disposição.

A função interna de cópia copia elementos de uma fatia de origem em uma fatia de destino. (Como um caso especial, ele também copiará bytes de uma string para uma fatia de bytes.) A origem e o destino podem se sobrepor. Copiar retorna o número de elementos copiados, que será o mínimo de len (src) e len (dst).

Mas quando eu faço:

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

Meu tmpestá vazio como estava antes (eu até tentei usar arr, tmp):

[]
[1 2 3]

Você pode verificá-lo no playground . Então, por que não consigo copiar uma fatia?

Salvador Dalí
fonte
obrigado a todos, é realmente triste que eu não tenha notado que as fatias devem ter o mesmo comprimento.
Salvador Dali
1
Não necessariamente o mesmo, mas dstdeve ter pelo menos o tamanho de muitos elementos que você deseja copiar (para uma cópia completa srcsignifica len(dst) >= len(src)).
Icza
2
b := append([]int{}, a...)
rocketspacer

Respostas:

209

O builtin copy(dst, src)copia min(len(dst), len(src))elementos.

Portanto, se o seu dstestiver vazio ( len(dst) == 0), nada será copiado.

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

Saída (conforme o esperado):

[1 2 3]
[1 2 3]

Infelizmente, isso não está documentado no builtinpacote, mas está documentado na especificação de idioma Go: anexando e copiando fatias :

O número de elementos copiados é o mínimo de len(src)e len(dst).

Editar:

Finalmente, a documentação de copy()foi atualizada e agora contém o fato de que o comprimento mínimo de origem e destino será copiado:

Copiar retorna o número de elementos copiados, que será o mínimo de len (src) e len (dst).

icza
fonte
2
Para resumir, copynão contém lógica para aumentar a fatia de destino se a fatia de destino for muito pequena, mas há outra função interna que faz isso: append embora neste exemplo seja melhor alocar a fatia de tamanho certo em primeiro lugar, appendpode ser usado quando você já tem uma fatia e deseja aumentá-la adicionando elementos ao final.
thomasrutter
1
Mas por que tenho que criar uma fatia de tamanho limitado ao copiar uma fatia de tamanho ilimitado?
2028 Alex
24

Outra maneira simples de fazer isso é usar o appendque alocará a fatia no processo.

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

Saída (conforme o esperado):

[1 2 3]
[1 2 3]

Portanto, um atalho para copiar array arrseriaappend([]int(nil), arr...)

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

Dave
fonte
8
O problema aqui é que, em exemplos do mundo real, que são muito maiores, o anexo alocará excesso de memória - a menos que esse array seja posteriormente preenchido com capacidade por algum processamento adicional - porque foi projetado para realocação eficiente por meio de chamadas repetidas. play.golang.org/p/5_6618xnXn observe que o limite (x) aumenta para 12, e não 10. agora veja o que acontece quando 1 valor é adicionado aos valores 1048576 play.golang.org/p/nz32JPehhl a capacidade aumenta em 2048 slots para 1050624, para acomodar apenas um valor adicional.
j. andrew shusta
12

Se suas fatias fossem do mesmo tamanho, funcionaria :

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

Daria:

3
[1 2 3]
[1 2 3]

Em " Go Slices: uso e componentes internos ":

A função de cópia suporta a cópia entre fatias de diferentes comprimentos ( copiará apenas até o menor número de elementos )

O exemplo usual é:

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

A cópia () é executada pelo menor tamanho de dst e src, portanto, você deve inicializar o dst com o comprimento desejado.

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

Resultado:

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

Você pode inicializar e copiar todos os elementos em uma linha usando append () em uma fatia nula.

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

Exemplo:

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

Resultado:

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

Comparando com alocação + copy (), para mais de 1.000 elementos, use append. Na verdade, abaixo de 1.000, a diferença pode ser negligenciada; faça uma regra geral, a menos que você tenha muitas fatias.

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
fonte
1
O acréscimo deve ser usado nos casos em que a matriz será aumentada por chamadas repetidas, pois alocará otimisticamente o excesso de capacidade em antecipação a isso. A cópia deve ser usada uma vez por matriz de entrada nos casos em que a matriz de resultados deve ser criada no tamanho exato e não é realocada novamente. play.golang.org/p/0kviwKmGzx você não compartilhou o código de referência que produziu esses resultados, por isso não posso confirmar ou negar sua validade, mas ignora esse aspecto mais importante.
j. andrew shusta
1
Você quer dizer 'fatia', não matriz . São coisas diferentes.
Inanc Gumus
2

A especificação da linguagem de programação Go

Anexando e copiando fatias

A cópia de função copia elementos de fatia de um src de origem para um dst de destino e retorna o número de elementos copiados. Ambos os argumentos devem ter o mesmo tipo de elemento T e devem ser atribuídos a uma fatia do tipo [] T. O número de elementos copiados é o mínimo de len (src) e len (dst). Como um caso especial, a cópia também aceita um argumento de destino atribuível ao tipo [] byte com um argumento de origem de um tipo de sequência. Este formulário copia os bytes da sequência na fatia de bytes.

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

tmpprecisa de espaço suficiente para arr. Por exemplo,

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

Resultado:

[1 2 3]
[1 2 3]
peterSO
fonte
0

Aqui está uma maneira de copiar uma fatia. Estou um pouco atrasado, mas há uma resposta mais simples e rápida do que a de Dave. Estas são as instruções geradas a partir de um código como o @ Dave. Estas são as instruções geradas pela minha. Como você pode ver, há muito menos instruções. O que é faz é apenas faz append(slice), que copia a fatia. 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)
}

Emite isso:

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
fonte