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

103

J'apprends Go en passant par A Tour of Go . L'un des exercices me demande de créer une tranche 2D de dylignes et de dxcolonnes 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 que parcourir chaque tranche pour l'initialiser est trop verbeux. Et si la tranche avait plus de dimensions, le code deviendrait peu maniable. Existe-t-il un moyen concis d'initialiser des tranches 2D (ou n dimensions) dans Go?

Hazrmard
la source

Réponses:

148

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

Une chose que vous pouvez simplifier est d'utiliser la for rangeconstruction:

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

Notez également que si vous initialisez votre tranche avec un littéral composite , vous obtenez ceci 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, à savoir que vous n'avez pas à énumérer toutes les valeurs, seulement celles qui ne sont pas les valeurs nulles du type d'élément de la tranche. Pour plus de détails à ce sujet, consultez Éléments clés dans l'initialisation du tableau golang .

Par exemple, si vous voulez une tranche où les 10 premiers éléments sont des zéros, puis suit 1et 2, elle peut être créée 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 des 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 de tableaux, vous n'avez pas à parcourir le tableau «externe» et à initialiser les tableaux «internes», car les tableaux ne sont pas des descripteurs mais des valeurs. Voir le billet de blog Tableaux, tranches (et chaînes): les mécanismes de 'append' pour plus de détails.

Essayez les exemples sur le Go Playground .

icza
la source
Étant donné que l'utilisation d'un tableau simplifie le code, j'aimerais le faire. Comment le spécifier dans une structure? J'obtiens cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valuequand j'essaye d'assigner le tableau à ce que je suppose que je dis que Go est une tranche.
Eric Lindsey
Je l'ai compris moi-même et j'ai édité la réponse pour ajouter les informations.
Eric Lindsey
1
@EricLindsey Bien que votre modification soit bonne, je vais toujours la rejeter car je ne veux pas encourager l'utilisation de tableaux simplement parce que l'initialisation est plus facile. Dans 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 une baie à une autre dans Go? Les tableaux ont également leur place, pour plus de détails, voir Pourquoi avoir des tableaux dans Go?
icza
assez juste, mais je crois que l’information a encore du mérite. Ce que j'essayais d'expliquer avec ma modification, c'est que si vous avez besoin de la flexibilité de dimensions différentes entre les objets, les tranches sont la voie à suivre. D'un autre côté, si vos informations sont structurées de manière rigide et seront toujours les mêmes, les tableaux sont non seulement plus faciles à initialiser, mais ils sont également plus efficaces. Comment pourrais-je améliorer la modification?
Eric Lindsey
@EricLindsey Je vois que vous avez fait une autre modification qui a déjà été rejetée par d'autres. Dans votre édition, vous disiez d'utiliser des tableaux pour avoir un accès plus rapide aux éléments. Notez que Go optimise beaucoup de choses, et ce n'est peut-être pas le cas, les tranches peuvent être tout aussi rapides. Pour plus de détails, consultez Array vs Slice: accès à la vitesse .
icza
12

Il existe deux façons d'utiliser des tranches pour créer une matrice. Jetons un coup d'œil aux différences entre eux.

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 makeappels successifs ne garantit pas que vous vous retrouverez avec une matrice contiguë, donc vous pouvez avoir la matrice divisée en mémoire. Pensons à un exemple avec deux routines Go qui pourraient causer ceci:

  1. La routine n ° 0 s'exécute make([][]int, n)pour obtenir de la mémoire allouée matrix, obtenant un morceau de mémoire de 0x000 à 0x07F.
  2. Ensuite, il démarre la boucle et effectue la première ligne make([]int, m), passant de 0x080 à 0x0FF.
  3. Dans la deuxième itération, il est préempté par le planificateur.
  4. Le planificateur donne au processeur la routine n ° 1 et il commence à s'exécuter. Celui-ci utilise également make(à ses propres fins) et obtient de 0x100 à 0x17F (juste à côté de la première ligne de la routine # 0).
  5. Après un certain temps, il est préempté et la routine # 0 recommence à s'exécuter.
  6. Il effectue la make([]int, m)correspondance avec la deuxième itération de boucle et obtient de 0x180 à 0x1FF pour la deuxième ligne. À ce stade, nous avons déjà deux lignes 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, 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 ci-dessus dans le Go Playground pour voir la différence dans la mémoire attribuée en utilisant les deux méthodes. Notez que je l'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 la suivante en mémoire d'une autre ligne. Ensuite, si votre programme parcourt les éléments de la matrice (pour les lire ou les écrire), il y aura probablement plus d'erreurs de cache (d'où une latence plus élevée) par rapport à la deuxième méthode en raison de la pire localisation des données. D'un autre côté, avec la deuxième méthode, il peut ne pas être possible d'obtenir un seul morceau de mémoire alloué pour la matrice, en raison de la fragmentation de la mémoire (morceaux répartis sur toute la mémoire), même si théoriquement il peut y avoir suffisamment de mémoire libre pour cela. .

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

Marcos Canales Mayo
la source
2
golang.org/doc/effective_go.html#slices montre une façon intelligente de faire la technique de la mémoire contiguë en tirant parti de la syntaxe native des tranches (par exemple, pas besoin de calculer explicitement les limites des tranches avec des expressions comme (i + 1) * m)
Magnus
0

Dans les réponses précédentes, nous n'avons pas considéré la situation où la longueur initiale est inconnue. Dans ce cas, vous pouvez utiliser la logique suivante pour créer une matrice

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
la source
1
Je ne suis pas sûr que ce soit dans les limites de l'OP de "manière concise", comme il l'a déclaré "Je pense que l'itération à travers chaque tranche pour l'initialiser est trop verbeuse".
Marcos Canales Mayo