Dlaczego nie mogę zduplikować plasterka za pomocą `copy ()`?

122

Muszę zrobić kopię wycinka w Go i po przeczytaniu dokumentów mam do dyspozycji funkcję kopiowania .

Wbudowana funkcja copy kopiuje elementy z wycinka źródłowego do wycinka docelowego. (W specjalnym przypadku kopiuje również bajty z łańcucha do fragmentu bajtów). Źródło i miejsce docelowe mogą się nakładać. Copy zwraca liczbę skopiowanych elementów, która będzie minimum len (src) i len (dst).

Ale kiedy robię:

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

Mój tmpjest pusty, jak wcześniej (próbowałem nawet użyć arr, tmp):

[]
[1 2 3]

Możesz to sprawdzić na placu zabaw . Dlaczego więc nie mogę skopiować kawałka?

Salvador Dali
źródło
dziękuję wszystkim, to naprawdę smutne, że nie zauważyłem, że plasterki powinny być tej samej długości.
Salvador Dali
1
Niekoniecznie to samo, ale dstpowinno mieć co najmniej tyle elementów, które chcesz skopiować (pełna kopia srcoznacza to len(dst) >= len(src)).
icza
2
b := append([]int{}, a...)
rocketspacer

Odpowiedzi:

211

Wbudowane copy(dst, src)kopiuje min(len(dst), len(src))elementy.

Więc jeśli twój dstjest pusty ( len(dst) == 0), nic nie zostanie skopiowane.

Wypróbuj 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)

Wyjście (zgodnie z oczekiwaniami):

[1 2 3]
[1 2 3]

Niestety nie jest to udokumentowane w builtinpakiecie, ale jest udokumentowane w specyfikacji języka Go: Dołączanie do plasterków i kopiowanie :

Liczba skopiowanych elementów to minimum len(src)i len(dst).

Edytować:

Ostatecznie copy()zaktualizowano dokumentację i obecnie zawiera ona fakt, że skopiowana zostanie minimalna długość źródła i celu:

Copy zwraca liczbę skopiowanych elementów, która będzie minimum len (src) i len (dst).

icza
źródło
2
Podsumowując, copynie zawiera logiki do powiększania wycinka docelowego, jeśli wycinek docelowy jest zbyt mały, ale istnieje inna wbudowana funkcja, która to robi: append Podczas gdy w tym przykładzie lepiej jest po prostu przydzielić wycinek o odpowiednim rozmiarze w pierwszej kolejności, appendmoże być używany, gdy masz już plasterek i chcesz go wyhodować, dodając elementy na końcu.
thomasrutter
1
Ale dlaczego podczas kopiowania plasterka o nieograniczonym rozmiarze muszę tworzyć plasterki o ograniczonym rozmiarze?
Alex
24

Innym prostym sposobem na to jest użycie, appendktóre przydzieli wycinek w procesie.

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

Wyjście (zgodnie z oczekiwaniami):

[1 2 3]
[1 2 3]

Więc skrótem do kopiowania tablicy arrbyłobyappend([]int(nil), arr...)

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

Dave
źródło
8
tu haczyk polega na tym, że w rzeczywistych przykładach, które są znacznie większe, funkcja append przydzieli nadmiar pamięci - chyba że ta tablica zostanie później wypełniona do pełna w wyniku dalszego przetwarzania - ponieważ jest zaprojektowana do wydajnej realokacji w przypadku powtarzających się wywołań. play.golang.org/p/5_6618xnXn zwróć uwagę, że limit (x) wzrasta do 12, a nie 10. Teraz zobacz, co się stanie, gdy 1 wartość zostanie dodana do 1048576 wartości play.golang.org/p/nz32JPehhl pojemność przeskakuje o 2048 miejsc do 1050624, aby pomieścić tylko jedną dodatkową wartość.
j. andrew shusta
12

Gdyby twoje plasterki były tego samego rozmiaru, zadziałałoby :

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

Dałby:

3
[1 2 3]
[1 2 3]

Z „ Go Slices: use and internals ”:

Funkcja kopiowania obsługuje kopiowanie między kawałkami o różnej długości ( kopiuje tylko do mniejszej liczby elementów )

Typowy przykład to:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
VonC
źródło
10

Copy () działa dla najmniejszej długości dst i src, więc musisz zainicjować dst do żądanej długości.

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

Wynik:

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

Możesz zainicjować i skopiować wszystkie elementy w jednej linii za pomocą funkcji append () do zerowego plasterka.

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

Przykład:

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

Wynik:

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

W porównaniu z alokacją + copy (), dla ponad 1000 elementów użyj dołączania. W rzeczywistości poniżej 1000 różnica może być zaniedbana, zrób to według zasady, chyba że masz wiele plasterków.

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
Esze
źródło
1
append powinno być używane w przypadkach, gdy tablica zostanie zwiększona przez powtarzające się wywołania, ponieważ optymistycznie przydzieli nadmiar pojemności w oczekiwaniu na to. kopia powinna być używana raz na tablicę wejściową w przypadkach, gdy tablica wynikowa powinna zostać utworzona do dokładnego rozmiaru i nie jest ponownie przydzielana. play.golang.org/p/0kviwKmGzx nie udostępniłeś kodu testu porównawczego, który wygenerował te wyniki, więc nie mogę potwierdzić ani zaprzeczyć jego słuszności, ale pomija on ten ważniejszy aspekt.
j. andrew shusta
1
Masz na myśli „plaster”, a nie tablicę . To różne rzeczy.
Inanc Gumus
2

Specyfikacja języka programowania Go

Dołączanie i kopiowanie plasterków

Funkcja copy kopiuje elementy wycinka ze źródła źródłowego do docelowego miejsca docelowego i zwraca liczbę skopiowanych elementów. Oba argumenty muszą mieć identyczny typ elementu T i muszą być przypisane do wycinka typu [] T. Liczba skopiowanych elementów to minimum len (src) i len (dst). W specjalnym przypadku funkcja copy akceptuje również argument docelowy, który można przypisać do typu [] bajt z argumentem źródłowym typu łańcuchowego. Ta forma kopiuje bajty z ciągu do wycinka bajtów.

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

tmppotrzebuje wystarczająco dużo miejsca arr. Na przykład,

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

Wynik:

[1 2 3]
[1 2 3]
peterSO
źródło
0

UWAGA: Jest to nieprawidłowe rozwiązanie, jak udowodnił @benlemasurier

Oto sposób na skopiowanie plasterka. Jestem trochę spóźniony, ale jest prostsza i szybsza odpowiedź niż @ Dave's. To są instrukcje wygenerowane z kodu takiego jak @ Dave's. To są instrukcje wygenerowane przeze mnie. Jak widać, instrukcji jest znacznie mniej. Po prostu robi append(slice), kopiuje kawałek. Ten 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)
}

Wyprowadza to:

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]
xilpex
źródło
1
To jest niepoprawne, jak pokazano tutaj: play.golang.org/p/q3CoEoaid6d . Oczekiwany wynik powinien odzwierciedlać odpowiedź @ Dave'a: play.golang.org/p/mgdJ4voSlpd
ben lemasurier
1
@benlemasurier - Huh ... Wygląda na to, że masz rację! Dzięki, że dałeś mi znać!
xilpex