Was ist eine übersichtliche Möglichkeit, ein 2D-Slice in Go zu erstellen?

103

Ich lerne Go, indem ich eine Tour of Go mache . In einer der dortigen Übungen werde ich gebeten, ein 2D-Slice mit dyZeilen und dxSpalten zu erstellen uint8. Mein aktueller Ansatz, der funktioniert, ist folgender:

a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

Ich denke, dass es zu ausführlich ist, jedes Slice zu durchlaufen, um es zu initialisieren. Und wenn das Slice mehr Dimensionen hätte, würde der Code unhandlich werden. Gibt es eine übersichtliche Möglichkeit, 2D- (oder n-dimensionale) Slices in Go zu initialisieren?

Hazrmard
quelle

Antworten:

148

Es gibt keinen prägnanteren Weg, was Sie getan haben, ist der "richtige" Weg; weil Scheiben immer eindimensional sind, aber zusammengesetzt werden können, um höherdimensionale Objekte zu konstruieren. Sehen Sie diese Frage für weitere Informationen: Go: Wie ist zwei Speicher Darstellung der dreidimensionalen Anordnung .

Eine Sache, die Sie vereinfachen können, ist die Verwendung des for rangeKonstrukts:

a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

Beachten Sie auch, dass Sie, wenn Sie Ihr Slice mit einem zusammengesetzten Literal initialisieren , dieses kostenlos erhalten, zum Beispiel:

a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

Ja, dies hat seine Grenzen, da Sie anscheinend alle Elemente aufzählen müssen. Es gibt jedoch einige Tricks, nämlich, dass Sie nicht alle Werte aufzählen müssen, sondern nur diejenigen, die nicht die Nullwerte des Elementtyps des Slice sind. Weitere Informationen hierzu finden Sie unter Schlüsselelemente in der Initialisierung des Golang-Arrays .

Wenn Sie beispielsweise ein Slice möchten, in dem die ersten 10 Elemente Nullen sind, und dann folgt 1und 2, kann es wie folgt erstellt werden:

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

Beachten Sie auch, dass Arrays anstelle von Slices sehr einfach erstellt werden können:

c := [5][5]uint8{}
fmt.Println(c)

Ausgabe ist:

[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

Bei Arrays müssen Sie nicht über das "äußere" Array iterieren und "innere" Arrays initialisieren, da Arrays keine Deskriptoren, sondern Werte sind. Weitere Informationen finden Sie im Blog-Beitrag Arrays, Slices (und Strings): Die Mechanik des Anhängens .

Probieren Sie die Beispiele auf dem Go Playground aus .

icza
quelle
Da die Verwendung eines Arrays den Code vereinfacht, würde ich das gerne tun. Wie spezifiziert man das in einer Struktur? Ich bekomme, cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valuewenn ich versuche, das Array dem zuzuweisen, was ich denke, dass Go ein Slice ist.
Eric Lindsey
Ich habe es selbst herausgefunden und die Antwort bearbeitet, um die Informationen hinzuzufügen.
Eric Lindsey
1
@EricLindsey Während Ihre Bearbeitung gut ist, werde ich sie dennoch ablehnen, weil ich die Verwendung von Arrays nicht fördern möchte, nur weil die Initialisierung einfacher ist. In Go sind Arrays zweitrangig, Slices sind der richtige Weg. Weitere Informationen finden Sie unter Was ist der schnellste Weg, um ein Array in Go an ein anderes anzuhängen? Arrays haben auch ihre Plätze. Weitere Informationen finden Sie unter Warum haben Arrays in Go?
icza
fair genug, aber ich glaube, die Informationen haben immer noch Wert. Was ich mit meiner Bearbeitung erklären wollte, war, dass Slices der richtige Weg sind, wenn Sie die Flexibilität unterschiedlicher Dimensionen zwischen Objekten benötigen. Wenn Ihre Informationen jedoch starr strukturiert sind und immer gleich sind, sind Arrays nicht nur einfacher zu initialisieren, sondern auch effizienter. Wie könnte ich die Bearbeitung verbessern?
Eric Lindsey
@EricLindsey Ich sehe, Sie haben eine weitere Bearbeitung vorgenommen, die bereits von anderen abgelehnt wurde. In Ihrer Bearbeitung haben Sie gesagt, dass Sie Arrays verwenden sollen, um einen schnelleren Elementzugriff zu erhalten. Beachten Sie, dass Go viele Dinge optimiert. Dies ist möglicherweise nicht der Fall. Slices sind möglicherweise genauso schnell. Weitere Informationen finden Sie unter Array vs Slice: Zugriff auf Geschwindigkeit .
icza
12

Es gibt zwei Möglichkeiten, Slices zum Erstellen einer Matrix zu verwenden. Schauen wir uns die Unterschiede zwischen ihnen an.

Erste Methode:

matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

Zweite Methode:

matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

In Bezug auf die erste Methode stellt das Tätigen aufeinanderfolgender makeAufrufe nicht sicher, dass Sie eine zusammenhängende Matrix erhalten, sodass die Matrix möglicherweise im Speicher aufgeteilt ist. Stellen wir uns ein Beispiel mit zwei Go-Routinen vor, die dies verursachen könnten:

  1. Die Routine Nr. 0 wird ausgeführt make([][]int, n), um den zugewiesenen Speicher matrixabzurufen, wobei ein Speicherelement von 0x000 bis 0x07F abgerufen wird.
  2. Dann startet es die Schleife und führt die erste Zeile aus make([]int, m), wobei von 0x080 bis 0x0FF gewechselt wird.
  3. In der zweiten Iteration wird es vom Scheduler vorbelegt.
  4. Der Scheduler gibt den Prozessor an Routine 1 weiter und startet ihn. Dieser verwendet auch make(für seine eigenen Zwecke) und erhält von 0x100 bis 0x17F (direkt neben der ersten Zeile der Routine # 0).
  5. Nach einer Weile wird es vorbelegt und Routine # 0 wird wieder ausgeführt.
  6. Es führt die make([]int, m)der zweiten Schleife entsprechende Iteration durch und erhält für die zweite Zeile 0x180 bis 0x1FF. Zu diesem Zeitpunkt haben wir bereits zwei geteilte Zeilen.

Bei der zweiten Methode versucht die Routine make([]int, n*m), die gesamte Matrix in einem einzigen Slice zuzuordnen, um die Kontiguität sicherzustellen. Danach wird eine Schleife benötigt, um die Matrixzeiger auf die Unterzeilen zu aktualisieren, die jeder Zeile entsprechen.

Sie können mit dem oben auf dem Go Playground gezeigten Code spielen , um den Unterschied im zugewiesenen Speicher mit beiden Methoden zu sehen. Beachten Sie, dass ich runtime.Gosched()nur verwendet habe, um den Prozessor zu erhalten und den Scheduler zu zwingen, zu einer anderen Routine zu wechseln.

Welches verwenden? Stellen Sie sich den schlimmsten Fall mit der ersten Methode vor, dh jede Zeile befindet sich nicht neben einer anderen Zeile im Speicher. Wenn Ihr Programm dann die Matrixelemente durchläuft (um sie zu lesen oder zu schreiben), treten im Vergleich zur zweiten Methode aufgrund der schlechteren Datenlokalität wahrscheinlich mehr Cache-Fehler (daher eine höhere Latenz) auf. Andererseits ist es mit der zweiten Methode aufgrund der Speicherfragmentierung (über den gesamten Speicher verteilte Blöcke) möglicherweise nicht möglich, ein einzelnes Stück Speicher für die Matrix zuzuweisen, obwohl theoretisch möglicherweise genügend freier Speicher dafür vorhanden ist .

Daher sollten Sie immer die zweite Methode verwenden, um die Datenlokalität zu nutzen, es sei denn, es gibt viel Speicherfragmentierung und die zuzuordnende Matrix ist groß genug.

Marcos Canales Mayo
quelle
2
golang.org/doc/effective_go.html#slices zeigt eine clevere Möglichkeit, die Technik des zusammenhängenden Speichers unter Verwendung der Slice-nativen Syntax durchzuführen (z. B. keine explizite Berechnung von Slice-Grenzen mit Ausdrücken wie (i + 1) * m)
Magnus
0

In früheren Antworten haben wir die Situation nicht berücksichtigt, in der die anfängliche Länge unbekannt ist. In diesem Fall können Sie die folgende Logik verwenden, um eine Matrix zu erstellen

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

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

Maxime
quelle
1
Ich bin mir nicht sicher, ob dies innerhalb der OP-Grenzen des "prägnanten Weges" liegt, da er sagte: "Ich denke, dass es zu ausführlich ist, jedes Slice zu durchlaufen, um es zu initialisieren."
Marcos Canales Mayo