Почему я не могу дублировать фрагмент с помощью `copy ()`?


122

Мне нужно сделать копию фрагмента в 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

Ответы:


211

Встроенная функция 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()была обновлена, и теперь она содержит тот факт, что минимальная длина источника и назначения будет скопирована:

Копирование возвращает количество скопированных элементов, которое будет минимум len (src) и len (dst).


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

1
Но почему я должен создавать фрагмент ограниченного размера при копировании фрагмента неограниченного размера?
Alex

24

Другой простой способ сделать это - использовать appendwhich, чтобы выделить фрагмент в процессе.

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
загвоздка здесь в том, что в реальных примерах, которые намного больше, append будет выделять избыточную память - если этот массив позже не будет заполнен до предела какой-либо дополнительной обработкой - потому что он предназначен для эффективного перераспределения при повторных вызовах. play.golang.org/p/5_6618xnXn обратите внимание, что cap (x) увеличивается до 12, а не 10. Теперь посмотрите, что происходит, когда 1 значение добавляется к 1048576 значениям play.golang.org/p/nz32JPehhl емкость увеличивается на 2048 слотов до 1050624, чтобы разместить только одно дополнительное значение.
Дж. Андрей Шуста 05

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: использование и внутреннее устройство »:

Функция копирования поддерживает копирование между срезами разной длины ( копирует только до меньшего количества элементов )

Обычный пример:

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

10

Copy () работает с наименьшей длиной 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]

По сравнению с allocation + copy () для более чем 1000 элементов используйте append. На самом деле ниже 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
append следует использовать в случаях, когда массив будет увеличиваться за счет повторных вызовов, так как он оптимистично распределяет избыточную емкость в ожидании этого. copy следует использовать один раз для каждого входного массива в тех случаях, когда результирующий массив должен быть создан с точным размером и не перераспределяется заново. play.golang.org/p/0kviwKmGzx вы не поделились кодом теста, который дал эти результаты, поэтому я не могу подтвердить или опровергнуть его достоверность, но он упускает из виду этот более важный аспект.
Дж. Андрей Шуста 05

1
Вы имеете в виду «срез», а не массив . Это разные вещи.
Inanc Gumus

2

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

Добавление и копирование фрагментов

Функция copy копирует элементы среза из исходного src в целевой dst и возвращает количество скопированных элементов. Оба аргумента должны иметь идентичный тип элемента T и должны быть назначены фрагменту типа [] T. Минимальное количество копируемых элементов: len (src) и len (dst). В качестве особого случая copy также принимает аргумент назначения, назначаемый байту type [] с исходным аргументом строкового типа. Эта форма копирует байты из строки в байтовый срез.

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

ПРИМЕЧАНИЕ. Это неправильное решение, как доказал @benlemasurier.

Вот способ скопировать срез. Я немного опоздал, но есть более простой и быстрый ответ, чем у @Dave. Это инструкции, сгенерированные из кода, подобного @ 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
ben lemasurier

1
@benlemasurier - А ... Кажется, ты прав! Спасибо, что дал мне знать!
xilpex
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.