Mengapa saya tidak dapat menduplikasi potongan dengan `copy ()`?


122

Saya perlu membuat salinan irisan di Go dan membaca dokumen ada fungsi salin yang dapat saya gunakan.

Fungsi bawaan salinan menyalin elemen dari potongan sumber ke potongan tujuan. (Sebagai kasus khusus, ini juga akan menyalin byte dari string ke potongan byte.) Sumber dan tujuan mungkin tumpang tindih. Salin mengembalikan jumlah elemen yang disalin, yang merupakan minimum len (src) dan len (dst).

Tetapi ketika saya melakukannya:

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

Saya tmpkosong seperti sebelumnya (saya bahkan mencoba menggunakan arr, tmp):

[]
[1 2 3]

Anda bisa mengeceknya di go playground . Jadi mengapa saya tidak dapat menyalin sepotong?


terima kasih semuanya, sungguh menyedihkan bahwa saya tidak memperhatikan bahwa irisan harus memiliki panjang yang sama.
Salvador Dali

1
Belum tentu sama, tetapi dstharus setidaknya sebesar banyak elemen yang ingin Anda salin (untuk salinan lengkap srcartinya len(dst) >= len(src)).
icza

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

Jawaban:


210

Elemen copy(dst, src)salinan bawaan min(len(dst), len(src)).

Jadi jika Anda dstkosong ( len(dst) == 0), tidak ada yang akan disalin.

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

Output (seperti yang diharapkan):

[1 2 3]
[1 2 3]

Sayangnya ini tidak didokumentasikan di dalam builtinpaket, tapi didokumentasikan di Go Language Spesifikasi: Menambahkan dan menyalin irisan :

Jumlah elemen yang disalin adalah minimum len(src)dan len(dst).

Edit:

Akhirnya dokumentasi copy()telah diperbarui dan sekarang berisi fakta bahwa panjang minimum sumber dan tujuan akan disalin:

Salin mengembalikan jumlah elemen yang disalin, yang merupakan minimum len (src) dan len (dst).


2
Singkatnya, copytidak berisi logika untuk menumbuhkan potongan tujuan jika potongan tujuan terlalu kecil, tetapi ada fungsi bawaan lain yang melakukannya: append Meskipun dalam contoh ini lebih baik mengalokasikan potongan berukuran tepat di tempat pertama, appenddapat digunakan jika Anda sudah memiliki potongan dan ingin mengembangkannya dengan menambahkan elemen di bagian akhir.
thomasrutter

1
Tetapi mengapa saya harus membuat potongan ukuran terbatas saat menyalin potongan berukuran tidak terbatas?
Alex

24

Cara sederhana lainnya untuk melakukan ini adalah dengan menggunakan appendyang akan mengalokasikan potongan dalam proses.

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

Output (seperti yang diharapkan):

[1 2 3]
[1 2 3]

Jadi cara cepat untuk menyalin array arradalahappend([]int(nil), arr...)

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


8
tangkapan di sini adalah bahwa dalam contoh dunia nyata, yang jauh lebih besar, append akan mengalokasikan memori berlebih - kecuali larik ini kemudian diisi ke kapasitasnya oleh beberapa pemrosesan lebih lanjut - karena dirancang untuk realokasi yang efisien melalui panggilan berulang. play.golang.org/p/5_6618xnXn amati bahwa cap (x) meningkat menjadi 12, bukan 10. sekarang lihat apa yang terjadi ketika 1 nilai ditambahkan ke 1048576 nilai play.golang.org/p/nz32JPehhl kapasitas melonjak 2048 slot ke 1050624, untuk menampung hanya satu nilai tambahan.
j. andrew shusta

12

Jika irisan Anda memiliki ukuran yang sama, itu akan berhasil :

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

Akan memberi:

3
[1 2 3]
[1 2 3]

Dari " Go Slices: penggunaan dan internal ":

Fungsi salin mendukung penyalinan di antara irisan dengan panjang berbeda ( hanya akan menyalin hingga jumlah elemen yang lebih kecil )

Contoh biasa adalah:

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

10

Copy () berjalan paling sedikit dari dst dan src, jadi Anda harus menginisialisasi dst ke panjang yang diinginkan.

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

Keluaran:

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

Anda bisa menginisialisasi dan menyalin semua elemen dalam satu baris menggunakan append () ke nil slice.

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

Contoh:

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

Keluaran:

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

Bandingkan dengan alokasi + copy (), untuk lebih dari 1.000 elemen, gunakan append. Sebenarnya di bawah 1.000 perbedaannya dapat diabaikan, lakukan aturan praktis kecuali Anda memiliki banyak potongan.

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 harus digunakan dalam kasus di mana larik akan ditingkatkan dengan panggilan berulang, karena secara optimis akan mengalokasikan kelebihan kapasitas untuk mengantisipasi hal ini. copy harus digunakan sekali per larik masukan dalam kasus di mana larik hasil harus dibuat dengan ukuran yang tepat, dan tidak dialokasikan kembali. play.golang.org/p/0kviwKmGzx Anda tidak membagikan kode benchmark yang menghasilkan hasil tersebut jadi saya tidak dapat mengkonfirmasi atau menyangkal validitasnya, tetapi mengabaikan aspek yang lebih penting ini.
j. andrew shusta

1
Maksudmu 'slice' bukan array . Mereka berbeda.
Inanc Gumus

2

Spesifikasi Bahasa Pemrograman Go

Menambahkan dan menyalin irisan

Copy fungsi menyalin elemen slice dari sumber src ke dst tujuan dan mengembalikan jumlah elemen yang disalin. Kedua argumen harus memiliki tipe elemen T yang identik dan harus dapat ditetapkan ke potongan tipe [] T. Jumlah elemen yang disalin adalah minimum len (src) dan len (dst). Sebagai kasus khusus, copy juga menerima argumen tujuan yang dapat ditetapkan ke tipe [] byte dengan argumen sumber dari tipe string. Formulir ini menyalin byte dari string ke dalam potongan byte.

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

tmpmembutuhkan cukup ruang untuk arr. Sebagai contoh,

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

Keluaran:

[1 2 3]
[1 2 3]

0

CATATAN: Ini adalah solusi yang salah karena @benlemasurier terbukti

Berikut adalah cara untuk menyalin potongan. Saya agak terlambat, tapi ada jawaban yang lebih sederhana dan lebih cepat dari @ Dave. Ini adalah instruksi yang dihasilkan dari kode seperti @ Dave's. Ini adalah instruksi yang dibuat oleh saya. Seperti yang Anda lihat, ada instruksi yang jauh lebih sedikit. Apa yang dilakukannya hanyalah melakukan append(slice), yang menyalin potongan. Kode ini:

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

Output ini:

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
Ini salah, seperti yang ditunjukkan di sini: play.golang.org/p/q3CoEoaid6d . Keluaran yang diharapkan harus mencerminkan jawaban @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier

1
@benlemasurier - Huh ... Sepertinya Anda benar! Terima kasih telah memberi tahu saya!
xilpex
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.