it-swarm.com.de

Was ist eine übersichtliche Methode zum Erstellen eines 2D-Schnitts in Go?

Ich lerne Go, indem ich A Tour of Go durchläuft. In einer der Übungen werde ich aufgefordert, ein 2D-Slice aus dy Zeilen und dx Spalten zu erstellen, das 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 der Slice mehr Dimensionen hätte, würde der Code unhandlich werden. Gibt es eine übersichtliche Möglichkeit, 2D-Schnitte (oder n-dimensionale Schnitte) in Go zu initialisieren?

57
hazrmard

Es gibt keinen präziseren Weg, was Sie getan haben, ist der "richtige" Weg. weil Schichten immer eindimensional sind, aber zusammengesetzt werden können, um höherdimensionale Objekte zu konstruieren. Weitere Informationen finden Sie in dieser Frage: Los: Wie ist die Speicherdarstellung des zweidimensionalen Arrays? .

Eine Sache, die Sie vereinfachen können, ist die Verwendung von for range bauen:

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

Beachten Sie auch, dass Sie, wenn Sie Ihr Slice mit einem zusammengesetztes Literal initialisieren, dies "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 scheinbar alle Elemente aufzählen müssen. Es gibt jedoch einige Tricks: Sie müssen nicht alle Werte auflisten, sondern nur diejenigen, die nicht Nullwerte des Elementtyps des Slice sind. Weitere Informationen hierzu finden Sie unter Eingegebene Elemente in der Golang-Array-Initialisierung .

Zum Beispiel, wenn Sie ein Slice wollen, in dem die ersten 10 Elemente Nullen sind und dann 1 und 2, es kann so 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, wenn Sie Arrays anstelle von Slices verwenden, dies sehr einfach erstellt werden kann:

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. Siehe Blog-Beitrag Arrays, Slices (und Strings): Die Mechanik von 'append' für weitere Details.

Probieren Sie die Beispiele auf dem Go Playground .

90
icza

Es gibt zwei Möglichkeiten, Slices zum Erstellen einer Matrix zu verwenden. Werfen wir einen Blick auf die Unterschiede zwischen ihnen.

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 Durchführen aufeinanderfolgender make -Aufrufe nicht sicher, dass Sie eine zusammenhängende Matrix erhalten, sodass die Matrix möglicherweise im Speicher aufgeteilt wird. Stellen wir uns ein Beispiel mit zwei Go-Routinen vor, die dies verursachen könnten:

  1. Die Routine # 0 führt make([][]int, n) aus, um den zugewiesenen Speicher für matrix abzurufen, wobei ein Teil des Speichers von 0x000 bis 0x07F abgerufen wird.
  2. Dann wird die Schleife gestartet und die erste Zeile make([]int, m) ausgeführt, wobei von 0x080 nach 0x0FF gewechselt wird.
  3. In der zweiten Iteration wird es vom Scheduler vorab geprüft.
  4. Der Scheduler übergibt den Prozessor an die Routine Nr. 1 und beginnt zu laufen. Dieser verwendet auch make (für seine eigenen Zwecke) und geht von 0x100 bis 0x17F (direkt neben der ersten Zeile der Routine # 0).
  5. Nach einer Weile wird es vorzeitig beendet und die Routine Nr. 0 wird wieder ausgeführt.
  6. Es führt die make([]int, m) aus, die der zweiten Schleifeniteration entspricht, und erhält von 0x180 bis 0x1FF für die zweite Zeile. Zu diesem Zeitpunkt haben wir bereits zwei unterteilte Zeilen.

Bei der zweiten Methode führt die Routine make([]int, n*m) aus, um die gesamte Matrix in einem einzigen Slice zuzuweisen, wodurch die Kontiguität sichergestellt wird. Danach wird eine Schleife benötigt, um die Matrixzeiger auf die Teilschnitte zu aktualisieren, die jeder Zeile entsprechen.

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

Welches verwenden? Stellen Sie sich den schlimmsten Fall mit der ersten Methode vor, d. H., Jede Zeile ist nicht die nächste Zeile im Speicher. Wenn Ihr Programm dann die Matrixelemente durchläuft (um sie zu lesen oder zu schreiben), kommt es wahrscheinlich aufgrund der schlechteren Datenlokalität zu mehr Cache-Fehlern (daher zu einer höheren Latenz) als bei der zweiten Methode. Andererseits kann es bei der zweiten Methode aufgrund der Speicherfragmentierung (über den gesamten Speicher verteilte Blöcke) möglicherweise nicht möglich sein, ein einzelnes Stück Speicher für die Matrix zuzuweisen, obwohl theoretisch genügend freier Speicher dafür vorhanden sein kann .

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

5

Sie können dieses Stück Code verweisen -

package main

import "fmt"

func main() {
    var row, col int
    fmt.Print("enter rows cols: ")
    fmt.Scan(&row, &col)

    // allocate composed 2d array
    a := make([][]int, row)
    for i := range a {
        a[i] = make([]int, col)
    }

    // array elements initialized to 0
    fmt.Println("a[0][0] =", a[0][0])

    // assign
    a[row-1][col-1] = 7

    // retrieve
    fmt.Printf("a[%d][%d] = %d\n", row-1, col-1, a[row-1][col-1])

    // remove only reference
    a = nil
    // memory allocated earlier with make can now be garbage collected.
}

Referenz

0
Techidiot