Tại sao tôi không thể sao chép một lát cắt với `copy ()`?


122

Tôi cần tạo một bản sao của một lát cắt trong Go và đọc tài liệu, có chức năng sao chép theo ý tôi.

Chức năng sao chép tích hợp sao chép các phần tử từ một lát nguồn vào một lát đích. (Là một trường hợp đặc biệt, nó cũng sẽ sao chép các byte từ một chuỗi sang một phần byte.) Nguồn và đích có thể trùng nhau. Copy trả về số phần tử được sao chép, sẽ là số lượng tối thiểu của len (src) và len (dst).

Nhưng khi tôi làm:

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

Của tôi tmptrống như trước đây (tôi thậm chí đã cố gắng sử dụng arr, tmp):

[]
[1 2 3]

Bạn có thể kiểm tra nó trên sân chơi go . Vậy tại sao tôi không thể sao chép một lát cắt?


cảm ơn tất cả mọi người, nó thực sự buồn rằng tôi đã không nhận thấy rằng các lát cắt nên có cùng độ dài.
Salvador Dali

1
Không nhất thiết phải giống nhau, nhưng dstít nhất phải lớn bằng nhiều phần tử bạn muốn sao chép (đối với một bản sao đầy đủ srccó nghĩa là len(dst) >= len(src)).
icza

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

Câu trả lời:


210

Các copy(dst, src)bản sao nội trangmin(len(dst), len(src)) các phần tử.

Vì vậy, nếu của bạn dsttrống ( len(dst) == 0), sẽ không có gì được sao chép.

Thử 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)

Đầu ra (như mong đợi):

[1 2 3]
[1 2 3]

Rất tiếc, điều này không được ghi lại trong builtingói, nhưng nó được ghi lại trong Đặc tả ngôn ngữ Go: Thêm và sao chép các lát :

Số phần tử được sao chép là tối thiểu của len(src)len(dst).

Biên tập:

Cuối cùng, tài liệu của copy()đã được cập nhật và hiện nó chứa thực tế là độ dài tối thiểu của nguồn và đích sẽ được sao chép:

Copy trả về số phần tử được sao chép, sẽ là số lượng tối thiểu của len (src) và len (dst).


2
Tóm lại, copykhông chứa logic để phát triển lát đích nếu lát đích quá nhỏ, nhưng có một chức năng tích hợp khác làm được: append Trong khi trong ví dụ này, tốt hơn là chỉ nên phân bổ lát có kích thước phù hợp ngay từ đầu, appendcó thể được sử dụng khi bạn đã có một lát cắt và muốn phát triển nó bằng cách thêm các phần tử vào cuối.
thomasrutter

1
Nhưng tại sao tôi phải tạo lát cắt có kích thước giới hạn khi sao chép lát cắt có kích thước không giới hạn?
Alex

24

Một cách đơn giản khác để làm điều này là sử dụng appendnó sẽ phân bổ lát cắt trong quy trình.

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

Đầu ra (như mong đợi):

[1 2 3]
[1 2 3]

Vì vậy, một cách viết tắt để sao chép mảng arrsẽ làappend([]int(nil), arr...)

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


8
lợi ích ở đây là trong các ví dụ trong thế giới thực, lớn hơn nhiều, append sẽ cấp phát bộ nhớ dư thừa - trừ khi mảng này sau đó được lấp đầy dung lượng bởi một số xử lý tiếp theo - vì nó được thiết kế để phân bổ lại hiệu quả qua các cuộc gọi lặp lại. play.golang.org/p/5_6618xnXn quan sát rằng giới hạn (x) tăng lên 12, không phải 10. Bây giờ hãy xem điều gì sẽ xảy ra khi 1 giá trị được thêm vào 1048576 giá trị play.golang.org/p/nz32JPehhl dung lượng sẽ tăng lên 2048 vị trí để 1050624, chỉ chứa một giá trị bổ sung.
j. andrew shusta

12

Nếu các lát cắt của bạn có cùng kích thước, nó sẽ hoạt động :

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

Sẽ cho:

3
[1 2 3]
[1 2 3]

Từ " Go Slices: sử dụng và nội bộ ":

Chức năng sao chép hỗ trợ sao chép giữa các lát có độ dài khác nhau ( nó sẽ chỉ sao chép tối đa số phần tử nhỏ hơn )

Ví dụ thông thường là:

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

10

Bản sao () chạy với độ dài nhỏ nhất của dst và src, vì vậy bạn phải khởi tạo dst với độ dài mong muốn.

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

Đầu ra:

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

Bạn có thể khởi tạo và sao chép tất cả các phần tử trong một dòng bằng append () vào một lát cắt nil.

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

Thí dụ:

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

Đầu ra:

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

So sánh với phân bổ + copy (), đối với hơn 1.000 phần tử, hãy sử dụng append. Trên thực tế, dưới 1.000 sự khác biệt có thể bị bỏ qua, hãy biến nó thành quy tắc ngón tay cái trừ khi bạn có nhiều lát cắt.

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 nên được sử dụng trong trường hợp mảng sẽ được tăng lên bởi các cuộc gọi lặp lại, vì nó sẽ phân bổ một cách lạc quan dung lượng dư thừa khi dự đoán điều này. bản sao phải được sử dụng một lần cho mỗi mảng đầu vào trong trường hợp mảng kết quả phải được tạo với kích thước chính xác và không được phân bổ lại từ một lần nữa. play.golang.org/p/0kviwKmGzx bạn đã không chia sẻ mã điểm chuẩn tạo ra những kết quả đó nên tôi không thể xác nhận hoặc phủ nhận tính hợp lệ của nó, nhưng nó bỏ qua khía cạnh quan trọng hơn này.
j. andrew shusta

1
Bạn có nghĩa là 'lát' không phải mảng . Chúng là những thứ khác nhau.
Inanc Gumus

2

Đặc tả ngôn ngữ lập trình Go

Thêm và sao chép các lát

Chức năng sao chép các phần tử của lát cắt từ một nguồn src đến một dst đích và trả về số lượng các phần tử được sao chép. Cả hai đối số phải có cùng kiểu phần tử T và phải được gán cho một lát cắt kiểu [] T. Số phần tử được sao chép là tối thiểu của len (src) và len (dst). Là một trường hợp đặc biệt, bản sao cũng chấp nhận đối số đích có thể gán cho kiểu byte [] với đối số nguồn là kiểu chuỗi. Biểu mẫu này sao chép các byte từ chuỗi vào lát cắt byte.

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

tmpcần đủ chỗ cho arr. Ví dụ,

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

Đầu ra:

[1 2 3]
[1 2 3]

0

LƯU Ý: Đây là một giải pháp không chính xác như @benlemasurier đã chứng minh

Đây là một cách để sao chép một lát cắt. Tôi hơi muộn, nhưng có một câu trả lời đơn giản và nhanh hơn @ Dave. Đây là các hướng dẫn được tạo từ mã như @ Dave's. Đây là hướng dẫn do tôi tạo ra. Như bạn có thể thấy, có ít hướng dẫn hơn. Những gì là không là nó chỉ làm append(slice), mà sao chép lát cắt. Mã này:

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

Kết quả này:

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
Điều này không chính xác, như hiển thị ở đây: play.golang.org/p/q3CoEoaid6d . Kết quả mong đợi phải phản ánh câu trả lời của @ Dave: play.golang.org/p/mgdJ4voSlpd
ben lemasurier

1
@benlemasurier - Hả ... Có vẻ bạn nói đúng! Cảm ơn vì đã cho tôi biết!
xilpex
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.