Comprendre les tableaux et les tranches en Go

Introduction

En Go, les tableaux et les slices sont des structures de données qui se composent d’une séquence ordonnée d’éléments. Ces collections de données sont très utiles lorsque vous voulez travailler avec de nombreux valeurs liées. Elles permettent de maintenir ensemble les données qui se rappellent, de condenser votre code et d’appliquer les mêmes méthodes et opérations sur plusieurs valeurs en même temps.

Bien que les tableaux et les slices en Go soient les deux des séquences ordonnées d’éléments, il existe des différences significatives entre les deux. Un tableau en Go est une structure de données qui se compose d’une séquence ordonnée d’éléments dont la capacité est définie au moment de la création. Une fois que le tableau a alloué sa taille, la taille ne peut plus être modifiée. En revanche, un slice est une version à longueur variable d’un tableau, offrant plus de flexibilité aux développeurs utilisant ces structures de données. Les slices constituent ce que vous pourriez considérer comme des tableaux dans d’autres langages.

Compte tenu de ces différences, il existe des situations spécifiques où l’une est préférable à l’autre. Si vous êtes nouveau en Go, déterminer quand utiliser chacune peut être confus : bien que la versatilité des slices les rend soit plus adaptées à la plupart des situations, il existe des instances où les tableaux peuvent optimiser les performances de votre programme.

Cet article couvrira en détail les tableaux et les slices, ce qui vous donnera les informations nécessaires pour faire un choix approprié entre ces types de données. De plus, vous passeriez en revue les manières les plus communes de déclarer et travailler à la fois avec les tableaux et les slices. Le tutoriel commencera par décrire les tableaux et comment manipuler ils, suivi d’une explication des slices et de leurs différences.

Tableaux

Les tableaux sont des structures de données de collecte avec un nombre d’éléments défini. Comme la taille d’un tableau est statique, la structure de données ne nécessite qu’une allocation de mémoire unique, contrairement à une structure de données de longueur variable qui doit allouer dynamiquement de la mémoire afin de pouvoir augmenter ou diminuer à l’avenir. Bien que la longueur fixe des tableaux puisse les rendre un peu rigide à traiter, l’allocation de mémoire unique peut augmenter la vitesse et la performance de votre programme. En raison de cela, les développeurs utilisent généralement des tableaux lors de l’optimisation de programmes dans des instances où la structure de données ne nécessitera jamais un nombre d’éléments variable.

Définition d’un Tableau

Les tableaux sont définis en déclarant la taille de l’array dans des parenthèses [ ], suivie du type de données des éléments. Un array en Go doit avoir tous ses éléments de même type de donnée. Après le type, vous pouvez déclarer les valeurs individuelles des éléments entre crochets { }.

Le schéma général pour déclarer un array est :

[capacity]data_type{element_values}

Note : Il est important de se souvenir que chaque déclaration d’un nouvel array crée un type distinct. Alors que [2]int et [3]int tous deux ont des éléments entiers, leur longueur différente rendent leurs types de données incompatibles.

Si vous ne déclarez pas les valeurs des éléments de l’array, la valeur par défaut est zéro, ce qui signifie que les éléments de l’array seront vide. Pour les entiers, cela est représenté par 0, et pour les chaines de caractères, cela est représenté par une chaine vide.

Par exemple, le suivant array numbers a trois éléments int qui n’ont pas encore une valeur :

var numbers [3]int

Si vous imprimeriez numbers, vous recevrez la sortie suivante :

Output
[0 0 0]

Si vous voulez affecter les valeurs des éléments lorsque vous créez l’array, placez les valeurs dans des crochets. Une array de chaines de caractères avec des valeurs fixées ressemble ainsi :

[4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}

Vous pouvez stocker un array dans une variable et l’afficher :

coral := [4]string{"blue coral", "staghorn coral", "pillar coral", "elkhorn coral"}
fmt.Println(coral)

Exécuter un programme avec les lignes précédentes donnera les résultats suivants :

Output
[blue coral staghorn coral pillar coral elkhorn coral]

Notez qu’il n’y a pas de délimitation entre les éléments de l’array lorsqu’il est affiché, ce qui rend difficile de déterminer où un élément se termine et où commence l’autre. En raison de cela, il est parfois utile d’utiliser la fonction fmt.Printf plutôt, qui permet de formatter les chaînes de caractères avant de les imprimer sur l’écran. Fournir le verbe %q avec cette commande pour instructer la fonction à placer des guillemets autour des valeurs :

fmt.Printf("%q\n", coral)

Cela donnera le résultat suivant :

Output
["blue coral" "staghorn coral" "pillar coral" "elkhorn coral"]

Maintenant, chaque élément est entre guillemets. Le verbe \n instructions au formatteur d’ajouter un retour à la ligne à la fin.

Avec une idée générale de comment déclarer des tableaux et de ce qu’ils composent, vous pouvez maintenant passer à l’apprentissage de comment spécifier les éléments d’un tableau à l’aide d’un numéro d’index.

Indexation des tableaux (et des slices)

Chaque élément dans un tableau (et également dans un slice) peut être appelé individuellement par indexation. Chaque élément correspond à un numéro d’index, qui est une valeur de type int commençant à l’index 0 et compte vers l’avant.

Nous utiliserons un tableau dans les exemples suivants, mais vous pourriez utiliser un slice également, car ils sont identiques en ce qui concerne l’indexation des deux.

Pour l’array coral, l’analyse de l’indexation se présente comme suit :

“blue coral” “staghorn coral” “pillar coral” “elkhorn coral”
0 1 2 3

Le premier élément, la chaîne de caractères "bleu corail", commence à l’index 0 et la séquence se termine à l’index 3 avec l’élément "élaghe corail".

Parce que chacun des éléments d’une séquence ou d’un tableau a un numéro d’index associé, nous pouvons accéder et manipuler les uns après les autres de la même manière que pour d’autres types de données en séquence.

Maintenant, nous pouvons appeler un élément individuel de la séquence en référençant son numéro d’index :

fmt.Println(coral[1])
Output
staghorn coral

Les numéros d’index pour cette séquence vont de 0-3, comme indiqué dans le tableau précédent. Donc, pour appeler n’importe quel élément individuellement, nous devrions faire référence à ces numéros ainsi :

coral[0] = "blue coral"
coral[1] = "staghorn coral"
coral[2] = "pillar coral"
coral[3] = "elkhorn coral"

Si nous appelons la variable coral avec un nombre d’index plus grand que 3, cela ne sera pas valide car il n’aura pas de sens :

fmt.Println(coral[18])
Output
panic: runtime error: index out of range

Lorsque vous indexez une array ou une slice, vous devez toujours utiliser un nombre positif. Contrairement à certaines langues où vous pouvez indexer arrière-plan avec un nombre négatif, ce qui conduira à une erreur en Go :

fmt.Println(coral[-1])
Output
invalid array index -1 (index must be non-negative)

Nous pouvons concatener des éléments de chaînes dans un array ou une slice avec d’autres chaînes en utilisant le opérateur + :

fmt.Println("Sammy loves " + coral[0])
Output
Sammy loves blue coral

Nous avons pu concaténer l’élément de la chaîne au numéro d’index 0 avec la chaîne "Sammy aime ".

Avec des numéros d’index qui correspondent aux éléments d’une array ou d’une slice, nous pouvons accéder à chaque élément discrétement et travailler avec ces éléments. Pour montrer ceci, nous allons examiner maintenant comment modifier un élément à un certain index.

Modification d’éléments

Nous pouvons utiliser l’indexation pour modifier les éléments à l’intérieur d’un tableau ou d’une slice en établissant un élément numéroté à une valeur différente. Cela nous donne plus de contrôle sur les données dans nos slices et tableaux et permettra de manipuler programmatiquement les éléments individuels.

Si nous voulons modifier la valeur de la chaîne de l’élément à l’index 1 du tableau coral de "staghorn coral" à "foliose coral", nous pouvons le faire comme suit :

coral[1] = "foliose coral"

Maintenant, lorsque nous affichons coral, le tableau sera différent :

fmt.Printf("%q\n", coral)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral"]

Maintenant que vous savez comment manipuler les éléments individuels d’un tableau ou d’une slice, voyons deux fonctions qui donneront plus de flexibilité lorsque vous travailliez avec les types de données de collection.

Compter les éléments avec len()

En Go, len() est une fonction native conçue pour vous aider à travailler avec des tableaux et des slices. Comme pour les chaînes de caractères, vous pouvez calculer la longueur d’un tableau ou d’une slice en utilisant len() et en passant le tableau ou la slice en paramètre.

Par exemple, pour trouver le nombre d’éléments dans le tableau coral, vous utilisez :

len(coral)

Si vous imprimer la longueur de l’array coral, vous recevrez les informations suivantes :

Output
4

Cela donne la longueur de l’array 4 dans le type de données int, ce qui est correct car l’array coral a quatre éléments :

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

Si vous créez un array d’entiers avec plus d’éléments, vous pouvez également utiliser la fonction len() sur cela :

numbers := [13]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
fmt.Println(len(numbers))

Ceci donnera les résultats suivants :

Output
13

Bien que ces exemples d’arrays soient relativement courts, la fonction len() est particulièrement utile lorsque vous voulez déterminer combien d’éléments sont dans des arrays très grands.

Après, nous allons couvrir comment ajouter une élément à un type de données collectif et montrer comment, en raison de la taille fixe des arrays, ajouter des éléments à ces types de données statiques résultera en erreur.

Ajout d’éléments avec append()

append() est une méthode intégrée dans Go qui permet d’ajouter des éléments à un type de collection. Cependant, cette méthode ne fonctionnera pas lorsqu’elle est utilisée avec un array. Comme mentionné précédemment, la principale différence entre les arrays et les slices est que la taille d’un array ne peut pas être modifiée. Cela signifie que, bien que vous puissiez changer les valeurs des éléments d’un array, vous ne pouvez pas faire que l’array soit plus grand ou plus petit après avoir été défini.

Considérons votre coral array :

coral := [4]string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral"}

Vous souhaitez ajouter l’élément « black coral » à ce tableau. Si vous essayez d’utiliser la fonction append() avec le tableau en tapant:

coral = append(coral, "black coral")

Vous recevrez une erreur car votre sortie:

Output
first argument to append must be slice; have [4]string

.Pour corriger cela, apprenez plus sur le type de données séquence, comment définir une séquence et comment convertir un tableau en séquence.

Séquences

Une séquence est un type de données en Go qui est mutable, ou changeable, ordonnée de éléments. Comme la taille d’une séquence est variable, il y a beaucoup plus de flexibilité lorsque vous travaillez avec des collectivités de données ; lorsque vous traitez avec des collections dont la taille peut nécessiter une modification ultérieure ou une contraction, utiliser une séquence assurera que votre code ne rencontrera pas d’erreurs lorsque vous essayez de manipuler la longueur de la collection. En général, cette mutabilité est une valeur ajoutée par rapport aux tableaux, même si les réallocations de mémoire occasionnelles peuvent être nécessaires lorsque vous travaillez avec des séquences. Quand vous avez besoin de stocker beaucoup d’éléments ou de boucler sur des éléments et que vous voulez facilement modifier ces éléments, vous devriez probablement travailler avec le type de données séquence.

Définition d’une Séquence

Les séquences sont définies en déclarant le type de données précédé par un ensemble de crochets vide ([]) et une liste d’éléments entre des accolades ({}). Vous pouvez remarquer que, contrairement aux tableaux qui nécessitent un int entre les crochets pour déclarer une longueur spécifique, une séquence n’a rien entre les crochets, représentant sa longueur variable.

Lets créer une séquence qui contient des éléments du type chaîne de caractères:

seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}

Quand nous imprimons la séquence, vous pouvez voir les éléments qui sont dans la séquence:

fmt.Printf("%q\n", seaCreatures)

Cela résultera dans le suivant:

Output
["shark" "cuttlefish" "squid" "mantis shrimp" "anemone"]

Si vous voulez créer une séquence de taille certaine sans remplir encore les éléments de la collection, vous pouvez utiliser la fonction intégrée make():

oceans := make([]string, 3)

Si vous imprimiez cette séquence, vous obtiendrez:

Output
["" "" ""]

Si vous voulez préallouer la mémoire pour une capacité certaine, vous pouvez passer un troisième argument à make():

oceans := make([]string, 3, 5)

Ceci va faire une séquence initialisée avec zéro et une capacité préallouée de 5 éléments.

Vous avez maintenant compris comment déclarer une séquence. Cependant, cela ne résoudra pas encore l’erreur que nous avions avec la tableau coral plus tôt. Pour utiliser la fonction append() avec coral, vous devrez d’abord apprendre comment découper des parties d’arrays.

Slicing Arrays into Slices

En utilisant les nombres d’index pour déterminer les points de début et de fin, vous pouvez appeler une sous-section des valeurs dans un tableau. Cela est appelé la sélection par tranches du tableau, et vous pouvez le faire en créant un intervalle d’index séparé par une colonne, sous la forme de [premier_index:deuxième_index]. Il est important de noter cependant que lorsqu’on sélectionne une tranche d’un tableau, on obtient une tranche, pas un tableau.

Supposons que vous voulez juste imprimer les éléments moyens du tableau coral sans les premiers et les derniers éléments. Vous pouvez faire ceci en créant une tranche commençant à l’indice 1 et se terminant juste avant l’indice 3 :

fmt.Println(coral[1:3])

Exécutez une programmation avec cette ligne et vous obtiendrez les résultats suivants:

Output
[foliose coral pillar coral]

Lorsque vous créez une tranche, comme dans [1:3], le premier nombre représente où la tranche commence (inclusif), et le deuxième nombre représente la somme du premier nombre et du nombre total d’éléments que vous souhaitez récupérer :

array[starting_index : (starting_index + length_of_slice)]

En ce cas, votre calcul serait ainsi:

array[1 : (1 + 2)]

Quel est ainsi la notation qui vous a conduit à celle-ci :

coral[1:3]

Si vous voulez définir le début ou la fin du tableau comme point de début ou de fin de la tranche, vous pouvez omettre l’une des deux nombres dans la syntaxe array[premier_index:second_index]. Par exemple, si vous voulez imprimer les trois premiers éléments du tableau coral — qui seront "bleu corail", "corail folioïde", et "corail pilulaire" — vous pouvez taper :

fmt.Println(coral[:3])

Cela affichera:

Output
[blue coral foliose coral pillar coral]

Cette fonction a imprimé le début de l’array, juste avant l’index 3.

Pour inclure tous les éléments à la fin d’un tableau, vous utilisez une syntaxe différente :

fmt.Println(coral[1:])

Ceci donnera la séquence suivante de tranches :

Output
[foliose coral pillar coral elkhorn coral]

Cette section a discuté de la façon de faire appel individuellement aux parties d’un tableau en utilisant la sélection par tranche. Ensuite, vous apprendrez comment utiliser la sélection pour convertir entièrement un tableau en une séquence.

Conversion d’une ArrayListe en Slices

Si vous créez un tableau et décidez que vous avez besoin de lui donner une longueur variable, vous pouvez le convertir en séquence. Pour convertir un tableau en séquence, utilisez le processus de sélection que vous avez appris dans la Sélectionner des Parties d’Arrays étape de ce tutoriel, mais cette fois-ci, sélectionnez toute la séquence en oubliant les deux nombres d’index qui détermineraient les extrémités :

coral[:]

Ne passez pas directement la variable coral à une séquence, car une fois qu’une variable est définie en Go, son type ne peut pas être changé. Pour contourner cela, vous pouvez copier tout le contenu du tableau dans une nouvelle variable comme une séquence :

coralSlice := coral[:]

Si vous imprimeriez coralSlice, vous recevriez la sortie suivante :

Output
[blue coral foliose coral pillar coral elkhorn coral]

Maintenant, essayez d’ajouter l’élément black coral comme dans la section du tableau, en utilisant append() avec la nouvelle séquence convertie :

coralSlice = append(coralSlice, "black coral")
fmt.Printf("%q\n", coralSlice)

Cela affichera la séquence avec l’élément ajouté :

Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral"]

Nous pouvons également ajouter plus d’un élément dans une seule instruction append() :

coralSlice = append(coralSlice, "antipathes", "leptopsammia")
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia"]

Pour combiner deux tranches ensemble, vous pouvez utiliser append(), mais vous devez élargir le second argument à ajouter en utilisant la syntaxe d’expansion ... :

moreCoral := []string{"massive coral", "soft coral"}
coralSlice = append(coralSlice, moreCoral...)
Output
["blue coral" "foliose coral" "pillar coral" "elkhorn coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

Maintenant que vous avez appris comment ajouter un élément à votre tranche, nous verrons comment en retirer un.

Retirer un Élément d’une Tranche

Contrairement à d’autres langages, Go ne fournit pas de fonctions internes pour retirer un élément d’une tranche. Les éléments doivent être retirés d’une tranche en les tranchant.

Pour retirer un élément, vous devez trancher les éléments avant cet élément, trancher les éléments après cet élément, puis les deux nouvelles tranches ensemble sans l’élément que vous souhaitez retirer.

Si i est l’index de l’élément à retirer, la forme de ce processus ressemblerait à ce qui suit :

slice = append(slice[:i], slice[i+1:]...)

De coralSlice, voyons retirer l’élément "écorce de corail". Cet élément se trouve à la position d’index 3.

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[4:]...)

fmt.Printf("%q\n", coralSlice)
Output
["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]

Maintenant, l’élément à la position d’index 3, la chaîne "écorce de corail", n’est plus dans notre tranche coralSlice.

Nous pouvons également supprimer une plage avec la même approche. Supposons que nous voulions supprimer non seulement l’élément "elkhorn coral", mais aussi "black coral" et "antipathes". Nous pouvons utiliser une plage dans l’expression pour accomplir cela :

coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[6:]...)

fmt.Printf("%q\n", coralSlice)

Ce code retirera les indices 3, 4, et 5 de la séquence :

Output
["blue coral" "foliose coral" "pillar coral" "leptopsammia" "massive coral" "soft coral"]

Maintenant que vous avez compris comment ajouter et supprimer des éléments d’une séquence, voyons comment mesurer le nombre d’éléments qu’une séquence peut contenir à tout moment.

Mesurer la capacité d’une séquence avec la fonction cap()

Comme les séquences ont une longueur variable, la méthode len() n’est pas la meilleure option pour déterminer la taille de ce type de données. Au lieu de cela, vous pouvez utiliser la fonction cap() pour découvrir la capacité d’une séquence. Cela montrera combien d’éléments une séquence peut contenir, qui est déterminé par la quantité de mémoire allouée pour cette séquence.

Attention : car la longueur et la capacité d’un tableau sont toujours identiques, la fonction cap() ne fonctionnera pas sur des tableaux.

Un usage commun de cap() consiste en création d’une séquence avec un nombre prédéfini d’éléments et puis remplir ces éléments de manière programmatique. Cela permet d’éviter potentiellement des allocations de mémoire inutiles qui pourraient arriver si vous utilisez append() pour ajouter des éléments au-delà de la capacité actuelle allouée.

Traduisons maintenant la scénario où nous voulons faire une liste de nombres, 0 au 3. Nous pouvons utiliser append() dans un boucle pour faire cela, ou nous pouvons préallouer d’abord la séquence et utiliser cap() pour boucler pour remplir les valeurs.

Pour commencer, examinons l’utilisation de append() :

numbers := []int{}
for i := 0; i < 4; i++ {
	numbers = append(numbers, i)
}
fmt.Println(numbers)
Output
[0 1 2 3]

Dans ce cas, nous avons créé une séquence et ensuite créé une boucle qui itérait quatre fois. Chaque itération ajoutait la valeur actuelle du variable de boucle i dans l’index de la séquence numbers. Cependant, cela pourrait conduire à des allocations de mémoire inutiles qui peuvent ralentir votre programme. Quand vous ajoutez à une séquence vide, chaque fois que vous faites appel à append(), le programme vérifie la capacité de la séquence. Si la valeur ajoutée fait dépasser cette capacité, le programme allouera de la mémoire supplémentaire pour en compter. Cela crée des surcharges supplémentaires dans votre programme et peut résulter en une exécution plus lente.

Maintenant, remplissez la séquence sans utiliser append() en préallouant une certaine longueur/capacité :

numbers := make([]int, 4)
for i := 0; i < cap(numbers); i++ {
	numbers[i] = i
}

fmt.Println(numbers)

Output
[0 1 2 3]

En ce cas, nous avons utilisé make() pour créer une séquence et lui ont donné une capacité préallouée de 4 éléments. Nous avons ensuite utilisé la fonction cap() dans la boucle pour itérer à travers chacun des éléments initialement zéros, les remplissant jusqu’à atteindre la capacité préallouée. Dans chaque boucle, nous plaçons la valeur actuelle de la variable de boucle i dans l’index de la séquence numbers.

Bien que les stratégies append() et cap() soient fonctionnellement équivalentes, l’exemple de cap() évite toute allocation de mémoire supplémentaire qui serait nécessaire si on utilisait la fonction append().

Slices multidimensionnels

Vous pouvez également définir des tranches qui consistent en autres tranches comme éléments, chaque liste entre crochets étant incluse dans les grands crochets du tranche parent. Les collections de tranches comme celles-ci sont appelées tranches multidimensionnelles. Elles peuvent être considérées comme représentant des coordonnées multidimensionnelles ; par exemple, une collection de cinq tranches chacune composée de six éléments peut représenter un carré avec une largeur horizontale de cinq et une hauteur verticale de six.

Voyons maintenant le suivant :

seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

Pour accéder à un élément dans cette tranche, nous devrons utiliser plusieurs indices, un pour chaque dimension du constructif :

fmt.Println(seaNames[1][0])
fmt.Println(seaNames[0][0])

Dans le code précédent, nous identifions d’abord l’élément au premier indice de la tranche au deuxième indice, puis nous indiquons l’élément au premier indice de la tranche au premier indice. Cela donnera le résultat suivant :

Output
Sammy shark

Les valeurs d’index suivantes pour les autres éléments individuels sont :

seaNames[0][0] = "shark"
seaNames[0][1] = "octopus"
seaNames[0][2] = "squid"
seaNames[0][3] = "mantis shrimp"

seaNames[1][0] = "Sammy"
seaNames[1][1] = "Jesse"
seaNames[1][2] = "Drew"
seaNames[1][3] = "Jamie"

Lorsque vous travaillez avec des tranches multidimensionnelles, il est important de garder à l’esprit que vous aurez besoin d’épingler plus d’un numéro d’index pour accéder à des éléments spécifiques dans la tranche imbriquée pertinente.

Conclusion

Dans ce tutoriel, vous avez appris les fondamentaux du travail avec des tableaux et des tranches en Go. Vous avez effectué plusieurs exercices pour montrer comment les tableaux sont fixes en longueur, tandis que les tranches sont variables en longueur, et découvert comment cette différence affecte l’utilisation situationnelle de ces structures de données.

Pour poursuivre l’étude des structures de données en Go, consultez notre article sur La compréhension des maps en Go, ou explorez la série complète Comment coder en Go.

Source:
https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go