211 votes

Quelle est la manière concise de créer une tranche 2D en Go ?

J'apprends Go en passant par Une visite de Go . Dans l'un des exercices, on me demande de créer une tranche 2D de l'image de l'entreprise. dy rangs et dx colonnes contenant uint8 . Mon approche actuelle, qui fonctionne, est la suivante :

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
}

Je pense qu'itérer à travers chaque tranche pour l'initialiser est trop verbeux. Et si la tranche avait plus de dimensions, le code deviendrait lourd. Existe-t-il un moyen concis d'initialiser des tranches 2D (ou n-dimensionnelles) en Go ?

304voto

icza Points 3857

Il n'y a pas de manière plus concise, ce que vous avez fait est la "bonne" manière, car les tranches sont toujours unidimensionnelles mais peuvent être composées pour construire des objets de plus grande dimension. Voir cette question pour plus de détails : Go : Comment est la représentation de la mémoire d'un tableau à deux dimensions .

Une chose que vous pouvez simplifier est d'utiliser la fonction for range construire :

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

Notez également que si vous initialisez votre tranche avec un fichier littéral composite vous l'obtenez "gratuitement", par exemple :

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

Oui, cela a ses limites car apparemment vous devez énumérer tous les éléments ; mais il y a quelques astuces, notamment vous n'avez pas à énumérer toutes les valeurs, seulement celles qui ne sont pas le valeurs nulles du type d'élément de la tranche. Pour plus de détails à ce sujet, voir Éléments clés dans l'initialisation des tableaux golang .

Par exemple, si vous voulez une tranche dont les 10 premiers éléments sont des zéros, puis les suivants 1 y 2 il peut être créé comme ceci :

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

Notez également que si vous utilisez tableaux au lieu de tranches il peut être créé très facilement :

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

La sortie est :

[[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]]

Dans le cas des tableaux, il n'est pas nécessaire d'itérer sur le tableau "extérieur" et d'initialiser les tableaux "intérieurs", car les tableaux ne sont pas des descripteurs mais des valeurs. Voir l'article de blog Tableaux, tranches (et chaînes de caractères) : Les mécanismes de l'appendice pour plus de détails.

Essayez les exemples sur le site Go Playground .

0 votes

Comme l'utilisation d'un tableau simplifie le code, j'aimerais le faire. Comment le spécifier dans un struct ? J'obtiens cannot use [5][2]string literal (type [5][2]string) as type [][]string in field value quand j'essaie d'assigner le tableau à ce que je suppose être une tranche.

0 votes

Je l'ai compris moi-même et j'ai modifié la réponse pour ajouter l'information.

1 votes

@EricLindsey Bien que votre modification soit bonne, je vais quand même la rejeter car je ne veux pas encourager l'utilisation des tableaux juste parce que l'initialisation est plus facile. En Go, les tableaux sont secondaires, les tranches sont la voie à suivre. Pour plus de détails, voir Quel est le moyen le plus rapide d'ajouter un tableau à un autre en Go ? Les tableaux ont aussi leur place, pour plus de détails, voir Pourquoi avoir des tableaux en Go ?

41voto

Il existe deux façons d'utiliser les tranches pour créer une matrice. Examinons les différences entre elles.

Première méthode :

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

Deuxième méthode :

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

En ce qui concerne la première méthode, faire des successions make Les appels ne garantissent pas que vous vous retrouverez avec une matrice contiguë, vous pouvez donc avoir la matrice divisée en mémoire. Prenons un exemple avec deux routines Go qui pourraient causer cela :

  1. La routine #0 exécute make([][]int, n) pour obtenir la mémoire allouée pour matrix en récupérant un morceau de mémoire de 0x000 à 0x07F.
  2. Ensuite, il démarre la boucle et fait la première ligne make([]int, m) en passant de 0x080 à 0x0FF.
  3. Dans la deuxième itération, il est préempté par l'ordonnanceur.
  4. Le planificateur donne le processeur à la routine #1 et celle-ci commence à fonctionner. Celle-ci utilise également make (pour ses propres besoins) et va de 0x100 à 0x17F (juste à côté de la première ligne de la routine #0).
  5. Après un certain temps, elle est préemptée et la routine 0 recommence à fonctionner.
  6. Il fait le make([]int, m) correspondant à la deuxième itération de la boucle et obtient de 0x180 à 0x1FF pour la deuxième ligne. À ce stade, nous avons déjà deux rangées divisées.

Avec la deuxième méthode, la routine fait make([]int, n*m) pour obtenir toute la matrice allouée dans une seule tranche, en assurant la contiguïté. Après cela, une boucle est nécessaire pour mettre à jour les pointeurs de matrice vers les sous tranches correspondant à chaque ligne.

Vous pouvez jouer avec le code montré ci-dessus dans la section Go Playground pour voir la différence dans la mémoire assignée en utilisant les deux méthodes. Notez que j'ai utilisé runtime.Gosched() uniquement dans le but de céder le processeur et de forcer le planificateur à passer à une autre routine.

Lequel utiliser ? Imaginez le pire des cas avec la première méthode, c'est-à-dire que chaque ligne n'est pas voisine en mémoire d'une autre ligne. Dans ce cas, si votre programme itère à travers les éléments de la matrice (pour les lire ou les écrire), il y aura probablement plus de manques dans le cache (donc une latence plus élevée) par rapport à la deuxième méthode en raison de la plus mauvaise localisation des données. D'autre part, avec la deuxième méthode, il peut être impossible d'obtenir un seul morceau de mémoire alloué à la matrice, en raison de la fragmentation de la mémoire (morceaux répartis dans toute la mémoire), même si, en théorie, il y a suffisamment de mémoire libre pour cela.

Par conséquent, à moins qu'il n'y ait beaucoup de fragmentation de la mémoire et que la matrice à allouer soit assez énorme, vous voudrez toujours utiliser la deuxième méthode pour profiter de la localité des données.

4 votes

golang.org/doc/effective_go.html#slices montre une façon intelligente de réaliser la technique de la mémoire contiguë en tirant parti de la syntaxe native des tranches (par exemple, il n'est pas nécessaire de calculer explicitement les limites des tranches avec des expressions comme (i+1)*m))

-1voto

Techidiot Points 1473

Vous pouvez vous référer à ce morceau de code -

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

Référence

-4voto

Voici une façon consive de le faire :

value := [][]string{}{[]string{}{"A1","A2"}, []string{}{"B1", "B2"}}

PS : vous pouvez remplacer "string" par le type d'élément que vous utilisez dans votre tranche.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X