Чому я не можу дублювати фрагмент з `copy ()`?


121

Мені потрібно зробити копію фрагмента в Go, і читаючи документи, у мене є функція копіювання .

Вбудована функція копіювання копіює елементи з вихідного фрагмента в цільовий фрагмент. (Як особливий випадок, він також буде копіювати байти з рядка в фрагмент байтів.) Джерело та призначення можуть перетинатися. Копія повертає кількість скопійованих елементів, яка буде мінімум 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]

Ви можете перевірити це на ігровому майданчику . То чому я не можу скопіювати фрагмент?


дякую всім, дуже сумно, що я не помітив, що скибочки повинні бути однакової довжини.
Сальвадор Далі

1
Не обов'язково однакові, але вони dstповинні бути принаймні такими ж великими, як і багато елементів, які ви хочете скопіювати (для повної копії srcце означає len(dst) >= len(src)).
icza

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

Відповіді:


209

Вбудований copy(dst, src)копіює min(len(dst), len(src))елементи.

Тож якщо ваш dstпорожній ( len(dst) == 0), нічого не буде скопійовано.

Спробуйте tmp := make([]int, len(arr))( Перейдіть на майданчик ):

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 Go: Додавання та копіювання фрагментів :

Кількість скопійованих елементів - мінімум len(src)та len(dst).

Редагувати:

Нарешті документація copy()була оновлена, і тепер вона містить факт, що буде скопійовано мінімальну довжину джерела та місця призначення:

Копія повертає кількість скопійованих елементів, яка буде мінімум len (src) та len (dst).


2
Підводячи підсумок, copyне міститься логіки для збільшення цільового фрагмента, якщо цільовий фрагмент занадто малий, але є ще одна вбудована функція, яка робить: append Хоча в цьому прикладі краще просто виділити фрагмент потрібного розміру в першу чергу, appendможна використовувати, коли у вас вже є фрагмент і хочете виростити його, додавши елементи до кінця.
thomasrutter

1
Але чому я повинен створювати зріз обмеженого розміру при копіюванні фрагмента необмеженого розміру?
Олексій

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 спостерігайте, що шапка (x) збільшується до 12, а не 10. тепер подивіться, що станеться, коли 1 значення додано до 1048576 значень play.golang.org/p/nz32JPehhl, ємність стрибне на 2048 слотів до 1050624, щоб врахувати лише одне додаткове значення.
j. 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]

З розділу " Перейти фрагменти: використання та внутрішні ресурси ":

Функція копіювання підтримує копіювання між фрагментами різної довжини ( копіюватиме лише до меншої кількості елементів )

Звичайний приклад:

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]

Ви можете ініціалізувати та скопіювати всі елементи в один рядок, використовуючи додавання () до нульового фрагмента.

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]

Порівнюючи з виділенням + copy () для більш ніж 1000 елементів, використовуйте додавання. Насправді нижче 1000 різницею можна знехтувати, нехай у вас є багато правил, якщо у вас багато скибочок.

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 ви не поділили контрольний код, який дав ці результати, тому я не можу підтвердити чи спростувати його дійсність, але він оглядає цей важливіший аспект.
j. andrew shusta

1
Ви маєте на увазі "фрагмент" не масив . Вони різні речі.
Інанк Гамус

2

Специфікація мови програмування Go

Додавання та копіювання фрагментів

Функція копіювання копіює елементи зрізу з вихідного src до призначення dst і повертає кількість скопійованих елементів. Обидва аргументи повинні мати однаковий елемент типу T і повинні бути віднесені до фрагменту типу [] T. Кількість скопійованих елементів - мінімум len (src) та len (dst). Як особливий випадок, копія також приймає аргумент призначення, який можна призначити байтом [] з вихідним аргументом рядкового типу. Ця форма копіює байти з рядка в байтовий фрагмент.

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

tmpпотребує достатньо місця для arr. Наприклад,

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

Ось спосіб скопіювати фрагмент. Я трохи запізнююся, але є простіша та швидша відповідь, ніж у @ Дейва. Це інструкції, згенеровані з такого коду, як @ 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.