Neden bir dilimi copy () ile çoğaltamıyorum?


122

Go'da bir dilimin bir kopyasını yapmam ve belgeleri okumam gerekiyor, orada bir kopyalama işlevi var.

Yerleşik kopyalama işlevi, bir kaynak dilimdeki öğeleri bir hedef dilime kopyalar. (Özel bir durum olarak, bir dizeden baytları bir bayt dilimine kopyalayacaktır.) Kaynak ve hedef çakışabilir. Copy, kopyalanan öğelerin sayısını döndürür; bu, minimum len (src) ve len (dst) olacaktır.

Ama yaptığımda:

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

Benim tmpo (Ben bile kullanım çalışmadan önce olduğu gibi boş arr, tmp):

[]
[1 2 3]

Oyun bahçesinde kontrol edebilirsiniz . Öyleyse neden bir dilimi kopyalayamıyorum?


herkese teşekkürler, dilimlerin aynı uzunlukta olması gerektiğini fark etmemiş olmam gerçekten üzücü.
Salvador Dali

1
İlle aynı değil, ama dsten azından sen (tam kopyası için kopyalamak istediğiniz birçok unsurları büyüklüğünde olmalıdır srcanlama len(dst) >= len(src)).
icza

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

Yanıtlar:


210

Yerleşik, öğeleri copy(dst, src)kopyalar min(len(dst), len(src)).

Yani dstboşsa ( len(dst) == 0), hiçbir şey kopyalanmayacaktır.

Deneyin tmp := make([]int, len(arr))( Oyun Alanına Git ):

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

Çıktı (beklendiği gibi):

[1 2 3]
[1 2 3]

Maalesef bu builtinpakette belgelenmemiştir , ancak Go Dil Belirtimi'nde belgelenmiştir : Dilimleri ekleme ve kopyalama :

Kopyalanan öğe sayısı minimum olduğu len(src)ve len(dst).

Düzenle:

Son olarak dokümantasyonu copy()güncellendi ve şimdi minimum kaynak ve hedef uzunluğunun kopyalanacağı gerçeğini içeriyor:

Copy, kopyalanan öğelerin sayısını döndürür; bu, minimum len (src) ve len (dst) olacaktır.


2
Özetlemek gerekirse, copyhedef dilim çok küçükse hedef dilimi büyütme mantığını içermez, ancak bunu yapan başka bir yerleşik işlev daha vardır: append Bu örnekte sadece doğru boyutlu dilimi ilk etapta ayırmak daha iyidir, appendZaten bir diliminiz olduğunda ve sonuna öğeler ekleyerek büyütmek istediğinizde kullanılabilir.
thomasrutter

1
Fakat sınırsız boyutlu bir dilimi kopyalarken neden sınırlı boyutlu bir dilim oluşturmam gerekiyor?
Alex

24

Bunu yapmanın başka bir basit yolu append, işlemde dilimi tahsis edecek olanı kullanmaktır .

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

Çıktı (beklendiği gibi):

[1 2 3]
[1 2 3]

Yani diziyi kopyalamak için bir kısaltmadır arrolurduappend([]int(nil), arr...)

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


8
Buradaki sorun, çok daha büyük olan gerçek dünya örneklerinde, append'in fazla belleği ayıracağıdır - bu dizi daha sonra bir miktar işlemle kapasiteye doldurulmadıkça - tekrarlanan aramalarda verimli yeniden tahsis için tasarlandığından. play.golang.org/p/5_6618xnXn cap (x) değerinin 10'a değil 12'ye yükseldiğini gözlemleyin. şimdi 1048576 değerine 1 değer eklendiğinde ne olduğuna bakın play.golang.org/p/nz32JPehhl kapasite 2048 yuva kadar sıçradı 1050624, yalnızca bir ek değeri barındırmak için.
j. andrew shusta

12

Dilimleriniz aynı boyutta olsaydı, işe yarar :

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

Verecekti:

3
[1 2 3]
[1 2 3]

" Go Slices: kullanım ve dahili öğeler " den:

Kopyalama işlevi, farklı uzunluktaki dilimler arasında kopyalamayı destekler ( yalnızca daha az sayıda öğeye kadar kopyalayacaktır )

Genel örnek şudur:

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

10

Copy (), en az dst ve src uzunluğu için çalışır, bu nedenle dst'yi istenen uzunlukta başlatmanız gerekir.

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

Çıktı:

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

Append () öğesini kullanarak bir sıfır dilime tüm öğeleri tek satırda başlatabilir ve kopyalayabilirsiniz.

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

Misal:

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

Çıktı:

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

1000'den fazla öğe için ayırma + kopya () ile karşılaştırıldığında, append'i kullanın. Aslında 1000'in altında fark ihmal edilebilir, çok sayıda diliminiz yoksa bunu bir kural olarak yapın.

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
Eklenti, dizinin tekrarlanan çağrılarla artırılacağı durumlarda, iyimser bir şekilde aşırı kapasiteyi bu beklentiyle tahsis edeceği durumlarda kullanılmalıdır. copy, sonuç dizisinin tam boyutta oluşturulması gerektiği ve yeniden ayrılmadığı durumlarda her girdi dizisi için bir kez kullanılmalıdır. play.golang.org/p/0kviwKmGzx bu sonuçları üreten kıyaslama kodunu paylaşmadınız, bu yüzden geçerliliğini onaylayamam veya reddedemem, ancak bu daha önemli yönü görmezden geliyor.
j. andrew shusta

1
Dizi değil 'dilim' demek istiyorsun . Farklı şeylerdir.
İnanç Gümüş

2

Go Programlama Dili Spesifikasyonu

Dilimleri eklemek ve kopyalamak

Copy işlevi, dilim öğelerini bir kaynak src'den bir hedef dst'ye kopyalar ve kopyalanan öğelerin sayısını döndürür. Her iki bağımsız değişken de aynı öğe türüne sahip olmalı ve [] T türündeki bir dilime atanabilir olmalıdır. Kopyalanan elemanların sayısı minimum len (src) ve len (dst) 'dir. Özel bir durum olarak, copy [] bayt türüne atanabilen bir hedef bağımsız değişkeni dizge türünde bir kaynak bağımsız değişkeniyle kabul eder. Bu form, baytları dizeden bayt dilimine kopyalar.

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

tmpiçin yeterli alana ihtiyacı var arr. Örneğin,

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

Çıktı:

[1 2 3]
[1 2 3]

0

NOT: @benlemasurier'ın kanıtladığı gibi bu yanlış bir çözümdür

İşte bir dilimi kopyalamanın bir yolu. Biraz geciktim ama @ Dave'inkinden daha basit ve daha hızlı bir cevap var. Bu , @ Dave'inki gibi bir koddan üretilen talimatlardır. Bunlar benim oluşturduğum talimatlardır. Gördüğünüz gibi çok daha az talimat var. Yaptığı şey append(slice), dilimi kopyalayan şeydir . Bu kod:

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

Şu çıktılar:

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
Burada gösterildiği gibi bu yanlıştır: play.golang.org/p/q3CoEoaid6d . Beklenen çıktı @ Dave'in cevabını yansıtmalıdır: play.golang.org/p/mgdJ4voSlpd
ben lemasurier

1
@benlemasurier - Huh ... Haklısın! Bilmeme izin verdiğin için teşekkürler!
xilpex
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.