เหตุใดฉันจึงไม่สามารถทำสำเนาชิ้นส่วนด้วย `copy ()` ได้?


122

ฉันต้องการทำสำเนาชิ้นส่วนใน Go และอ่านเอกสารมีฟังก์ชั่นการคัดลอกให้ฉันใช้

ฟังก์ชันคัดลอกในตัวจะคัดลอกองค์ประกอบจากชิ้นส่วนต้นทางไปยังชิ้นส่วนปลายทาง (ในกรณีพิเศษมันจะคัดลอกไบต์จากสตริงไปยังส่วนของไบต์ด้วย) ต้นทางและปลายทางอาจทับซ้อนกัน Copy ส่งคืนจำนวนองค์ประกอบที่คัดลอกซึ่งจะเป็นค่าต่ำสุดของ len (src) และ len (dst)

แต่เมื่อฉันทำ:

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

ของฉันtmpว่างเปล่าเหมือนเดิม (ฉันพยายามใช้ด้วยซ้ำarr, tmp):

[]
[1 2 3]

คุณสามารถตรวจสอบที่ไปสนามเด็กเล่น เหตุใดฉันจึงไม่สามารถคัดลอกชิ้นส่วนได้?


ขอบคุณทุกคนเป็นเรื่องน่าเศร้าจริงๆที่ฉันไม่ได้สังเกตว่าชิ้นส่วนควรมีความยาวเท่ากัน
Salvador Dali

1
ไม่จำเป็นต้องเหมือนกัน แต่dstอย่างน้อยควรมีขนาดใหญ่เท่ากับองค์ประกอบจำนวนมากที่คุณต้องการคัดลอก (สำหรับสำเนาทั้งหมดsrcหมายถึงlen(dst) >= len(src))
icza

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

คำตอบ:


210

องค์ประกอบในตัวจะcopy(dst, src)คัดลอกmin(len(dst), len(src))องค์ประกอบ

ดังนั้นหากคุณdstว่างเปล่า ( len(dst) == 0) จะไม่มีการคัดลอก

ลอง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)

เอาต์พุต (ตามคาด):

[1 2 3]
[1 2 3]

น่าเสียดายที่สิ่งนี้ไม่ได้รับการบันทึกไว้ในbuiltinแพ็คเกจ แต่มีการบันทึกไว้ในข้อกำหนดภาษา Go: การผนวกและคัดลอกส่วน :

จำนวนขององค์ประกอบที่คัดลอกขั้นต่ำของและlen(src)len(dst)

แก้ไข:

ในที่สุดเอกสารของcopy()ได้รับการอัปเดตและตอนนี้มีข้อเท็จจริงที่ว่าความยาวขั้นต่ำของต้นทางและปลายทางจะถูกคัดลอก:

Copy ส่งคืนจำนวนองค์ประกอบที่คัดลอกซึ่งจะเป็นค่าต่ำสุดของ len (src) และ len (dst)


2
สรุปได้ว่าcopyไม่มีตรรกะในการขยายชิ้นส่วนปลายทางหากชิ้นส่วนปลายทางมีขนาดเล็กเกินไป แต่มีฟังก์ชันในตัวอีกอย่างที่ทำ: append ในตัวอย่างนี้จะดีกว่าเพียงแค่จัดสรรชิ้นส่วนขนาดที่เหมาะสมตั้งแต่แรกappendสามารถใช้ได้เมื่อคุณมีชิ้นส่วนอยู่แล้วและต้องการขยายโดยการเพิ่มองค์ประกอบที่ส่วนท้าย
thomasrutter

1
แต่ทำไมฉันต้องสร้างชิ้นส่วนขนาดที่มีขอบเขตเมื่อคัดลอกชิ้นส่วนขนาดที่ไม่ถูกผูกไว้
Alex

24

อีกวิธีง่ายๆในการทำเช่นนี้คือการใช้appendซึ่งจะจัดสรรชิ้นส่วนในกระบวนการ

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

เอาต์พุต (ตามคาด):

[1 2 3]
[1 2 3]

ดังนั้นชวเลขสำหรับการคัดลอกอาร์เรย์arrจะเป็นappend([]int(nil), arr...)

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


8
สิ่งที่จับได้คือในตัวอย่างของโลกแห่งความจริงซึ่งมีขนาดใหญ่กว่ามากการผนวกจะจัดสรรหน่วยความจำส่วนเกิน - เว้นแต่อาร์เรย์นี้จะถูกเติมเต็มความจุในภายหลังโดยการประมวลผลเพิ่มเติมบางส่วน - เนื่องจากได้รับการออกแบบมาเพื่อการจัดสรรใหม่ที่มีประสิทธิภาพในการเรียกซ้ำ play.golang.org/p/5_6618xnXnสังเกตว่า cap (x) เพิ่มขึ้นเป็น 12 ไม่ใช่ 10 ตอนนี้ดูว่าจะเกิดอะไรขึ้นเมื่อเพิ่ม 1 ค่าเป็น 1048576 ค่าplay.golang.org/p/nz32JPehhlความจุจะเพิ่มขึ้น 2048 สล็อตเป็น 1050624 เพื่อรองรับมูลค่าเพิ่มเติมเพียงค่าเดียว
ญ. andrew shusta

12

หากชิ้นส่วนของคุณมีขนาดเท่ากันก็จะใช้ได้ :

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

จะให้:

3
[1 2 3]
[1 2 3]

จาก " Go Slices: usage and internalals ":

ฟังก์ชันคัดลอกรองรับการคัดลอกระหว่างชิ้นส่วนที่มีความยาวต่างกัน ( จะคัดลอกเฉพาะองค์ประกอบจำนวนน้อยกว่าเท่านั้น )

ตัวอย่างปกติคือ:

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

10

สำเนา () รันด้วยความยาวน้อยที่สุดของ dst และ src ดังนั้นคุณต้องเริ่มต้น dst ตามความยาวที่ต้องการ

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

เอาท์พุท:

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

คุณสามารถเริ่มต้นและคัดลอกองค์ประกอบทั้งหมดในบรรทัดเดียวโดยใช้ append () ไปยังส่วนศูนย์

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

ตัวอย่าง:

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

เอาท์พุท:

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

เมื่อเทียบกับการจัดสรร + สำเนา () สำหรับองค์ประกอบมากกว่า 1,000 รายการให้ใช้ผนวก จริงๆแล้วการร้อง 1,000 ความแตกต่างอาจถูกละเลยให้เป็นไปตามกฎทั่วไปเว้นแต่คุณจะมีหลายชิ้น

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
ควรใช้การผนวกในกรณีที่อาร์เรย์จะเพิ่มขึ้นโดยการเรียกซ้ำเนื่องจากจะจัดสรรความจุส่วนเกินในแง่ดีในแง่ดี ควรใช้สำเนาหนึ่งครั้งต่ออาร์เรย์อินพุตในกรณีที่ควรสร้างอาร์เรย์ผลลัพธ์ให้มีขนาดที่แน่นอนและจะไม่ถูกจัดสรรใหม่จากอีกครั้ง play.golang.org/p/0kviwKmGzxคุณไม่ได้แชร์โค้ดมาตรฐานที่ให้ผลลัพธ์เหล่านั้นดังนั้นฉันจึงไม่สามารถยืนยันหรือปฏิเสธความถูกต้องได้ แต่จะมองข้ามสิ่งที่สำคัญกว่านี้ไป
ญ. andrew shusta

1
คุณหมายถึง'เชือด'ไม่อาร์เรย์ พวกเขาเป็นสิ่งที่แตกต่างกัน
Inanc Gumus

2

ข้อกำหนดภาษาโปรแกรม Go

ต่อท้ายและคัดลอกชิ้นส่วน

การคัดลอกฟังก์ชันจะคัดลอกองค์ประกอบส่วนจาก src ต้นทางไปยังปลายทาง dst และส่งคืนจำนวนองค์ประกอบที่คัดลอก อาร์กิวเมนต์ทั้งสองต้องมีองค์ประกอบที่เหมือนกันประเภท T และต้องกำหนดให้กับชิ้นส่วนประเภท [] T จำนวนองค์ประกอบที่คัดลอกคือ len (src) และ len (dst) ขั้นต่ำ ในกรณีพิเศษสำเนายังยอมรับอาร์กิวเมนต์ปลายทางที่กำหนดให้กับชนิด [] ไบต์ด้วยอาร์กิวเมนต์ต้นทางของประเภทสตริง แบบฟอร์มนี้คัดลอกไบต์จากสตริงลงในชิ้นส่วนไบต์

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

tmparrความต้องการที่ว่างพอสำหรับ ตัวอย่างเช่น,

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

เอาท์พุท:

[1 2 3]
[1 2 3]

0

หมายเหตุ:นี่เป็นวิธีแก้ปัญหาที่ไม่ถูกต้องตามที่ @benlemasurier พิสูจน์แล้ว

นี่คือวิธีการคัดลอกชิ้นส่วน ฉันช้าไปหน่อย แต่มีคำตอบที่ง่ายและเร็วกว่า @ Dave's นี่คือคำแนะนำที่สร้างขึ้นจากรหัสเช่น @ Dave's นี่คือคำแนะนำที่สร้างขึ้นโดยฉัน อย่างที่คุณเห็นมีคำแนะนำน้อยกว่ามาก มันคืออะไรappend(slice)ที่ทำสำเนาชิ้นส่วน รหัสนี้:

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

ผลลัพธ์นี้:

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
นี้ไม่ถูกต้องตามที่แสดงที่นี่: play.golang.org/p/q3CoEoaid6d ผลลัพธ์ที่คาดหวังควรสะท้อนให้เห็นคำตอบของ @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier

1
@benlemasurier - หึ ... เห็นถูกแล้ว! ขอบคุณสำหรับการให้ฉันรู้ว่า!
xilpex
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.