Warum kann ich ein Slice nicht mit "copy ()" duplizieren?

121

Ich muss eine Kopie eines Slice in Go erstellen und beim Lesen der Dokumente steht mir eine Kopierfunktion zur Verfügung.

Die integrierte Funktion zum Kopieren kopiert Elemente aus einem Quell-Slice in ein Ziel-Slice. (Als Sonderfall werden auch Bytes aus einer Zeichenfolge in ein Byte-Slice kopiert.) Quelle und Ziel können sich überschneiden. Kopieren gibt die Anzahl der kopierten Elemente zurück, dh das Minimum von len (src) und len (dst).

Aber wenn ich es mache:

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

Mein tmpist leer wie vorher (ich habe sogar versucht es zu benutzen arr, tmp):

[]
[1 2 3]

Sie können es auf dem Spielplatz überprüfen . Warum kann ich ein Slice nicht kopieren?

Salvador Dali
quelle
Vielen Dank an alle, es ist wirklich traurig, dass ich nicht bemerkt habe, dass Scheiben gleich lang sein sollten.
Salvador Dali
1
Nicht unbedingt gleich, dstsollte aber mindestens so groß sein wie viele Elemente, die Sie kopieren möchten (für eine vollständige Kopie srcbedeutet dies len(dst) >= len(src)).
icza
2
b := append([]int{}, a...)
Raketenabstandhalter

Antworten:

209

Die eingebauten copy(dst, src)Kopienmin(len(dst), len(src)) Elemente.

Wenn Ihr dstalso leer ist ( len(dst) == 0), wird nichts kopiert.

Versuchen Sie 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)

Ausgabe (wie erwartet):

[1 2 3]
[1 2 3]

Leider ist dies nicht im builtinPaket dokumentiert , aber es ist in der Go-Sprachspezifikation dokumentiert : Anhängen an und Kopieren von Slices :

Die Anzahl der kopierten Elemente ist das Minimum von len(src)und len(dst).

Bearbeiten:

Schließlich wurde die Dokumentation von copy()aktualisiert und enthält nun die Tatsache, dass die Mindestlänge von Quelle und Ziel kopiert wird:

Kopieren gibt die Anzahl der kopierten Elemente zurück, dh das Minimum von len (src) und len (dst).

icza
quelle
2
Zusammenfassend copylässt sich sagen, dass es keine Logik zum Vergrößern des Ziel-Slice enthält, wenn das Ziel-Slice zu klein ist. Es gibt jedoch eine weitere integrierte Funktion, die Folgendes bewirkt: append In diesem Beispiel ist es besser, zunächst nur das Slice mit der richtigen Größe zuzuweisen. appendKann verwendet werden, wenn Sie bereits ein Slice haben und es vergrößern möchten, indem Sie am Ende Elemente hinzufügen.
Thomasrutter
1
Aber warum muss ich ein Slice mit begrenzter Größe erstellen, wenn ich ein Slice mit unbegrenzter Größe kopiere?
Alex
24

Eine andere einfache Möglichkeit, dies zu tun, besteht darin, appenddas Slice im Prozess zuzuweisen.

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

Ausgabe (wie erwartet):

[1 2 3]
[1 2 3]

Eine Abkürzung zum Kopieren von Arrays arrwäre alsoappend([]int(nil), arr...)

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

Dave
quelle
8
Der Haken dabei ist, dass in realen Beispielen, die viel größer sind, durch Anhängen überschüssiger Speicher zugewiesen wird - es sei denn, dieses Array wird später durch weitere Verarbeitung voll ausgelastet -, da es für eine effiziente Neuzuweisung bei wiederholten Aufrufen ausgelegt ist. play.golang.org/p/5_6618xnXn beobachten, dass die Obergrenze (x) auf 12 und nicht auf 10 erhöht wird. Schauen Sie nun, was passiert, wenn 1 Wert zu 1048576 Werten addiert wird. play.golang.org/p/nz32JPehhl springt die Kapazität um 2048 Slots auf 1050624, um nur einen zusätzlichen Wert aufzunehmen.
j. Andrew Shusta
12

Wenn Ihre Scheiben die gleiche Größe hätten, würde es funktionieren :

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

Würde geben:

3
[1 2 3]
[1 2 3]

Aus " Go Slices: Verwendung und Interna ":

Die Kopierfunktion unterstützt das Kopieren zwischen Slices unterschiedlicher Länge ( es wird nur bis zu einer geringeren Anzahl von Elementen kopiert ).

Das übliche Beispiel ist:

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

Die copy () wird für die kleinste Länge von dst und src ausgeführt, daher müssen Sie die dst auf die gewünschte Länge initialisieren.

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

Ausgabe:

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

Sie können alle Elemente in einer Zeile mit append () initialisieren und in ein Null-Slice kopieren.

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

Beispiel:

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

Ausgabe:

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

Verwenden Sie im Vergleich zu Zuordnung + Kopie () für mehr als 1.000 Elemente Anhängen. Unter 1000 kann der Unterschied vernachlässigt werden. Machen Sie es zur Faustregel, es sei denn, Sie haben viele Scheiben.

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
quelle
1
append sollte in Fällen verwendet werden, in denen das Array durch wiederholte Aufrufe vergrößert wird, da es in Erwartung dessen optimistisch überschüssige Kapazität zuweist. Die Kopie sollte einmal pro Eingabearray verwendet werden, wenn das Ergebnisarray auf die exakte Größe erstellt werden soll und nicht erneut zugewiesen wird. play.golang.org/p/0kviwKmGzx Sie haben den Benchmark-Code, der diese Ergebnisse erzeugt hat, nicht geteilt, sodass ich seine Gültigkeit nicht bestätigen oder leugnen kann, aber er übersieht diesen wichtigeren Aspekt.
j. Andrew Shusta
1
Du meinst 'Slice' nicht Array . Das sind verschiedene Dinge.
Inanc Gumus
2

Die Go-Programmiersprachenspezifikation

Anhängen an und Kopieren von Slices

Die Funktion copy kopiert Slice-Elemente von einem Quell-src in ein Ziel-dst und gibt die Anzahl der kopierten Elemente zurück. Beide Argumente müssen den identischen Elementtyp T haben und einem Slice vom Typ [] T zuweisbar sein. Die Anzahl der kopierten Elemente ist das Minimum von len (src) und len (dst). Als Sonderfall akzeptiert copy auch ein Zielargument, das dem Typ [] byte mit einem Quellargument eines Zeichenfolgentyps zugewiesen werden kann. Dieses Formular kopiert die Bytes aus der Zeichenfolge in das Byte-Slice.

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

tmpbraucht genug Platz für arr. Beispielsweise,

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

Ausgabe:

[1 2 3]
[1 2 3]
peterSO
quelle
0

Hier ist eine Möglichkeit, ein Slice zu kopieren. Ich bin etwas spät dran, aber es gibt eine einfachere und schnellere Antwort als bei @ Dave. Dies sind die Anweisungen, die aus einem Code wie @ Dave's generiert wurden. Dies sind die Anweisungen, die von mir generiert wurden. Wie Sie sehen, gibt es weit weniger Anweisungen. Was ist, ist, dass es nur tut append(slice), was das Slice kopiert. Dieser Code:

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

Gibt dies aus:

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
quelle