Comprendere gli array e le slice in Go

Introduzione

In Go, array e slice sono strutture dati che consistono in una sequenza ordinata di elementi. Questi insiemi di dati sono grandiose da utilizzare quando si vuole lavorare con molti valori relazionati. Permettono di tenere insieme i dati che appartengono insieme, comprimendo il codice e eseguendo gli stessi metodi e operazioni su molti valori contemporaneamente.

Anche se array e slice in Go sono entrambe sequenze ordinate di elementi, ci sono differenze significative tra i due. Un array in Go è una struttura dati che consiste in una sequenza ordinata di elementi con la sua capacità definita al momento della creazione. Una volta che l’array ha allocato la sua dimensione, la dimensione non può più essere cambiata. Un slice, d’altro canto, è una versione a lunghezza variabile di un array, fornendo maggiore flessibilità ai sviluppatori che utilizzano queste strutture dati. I slice costituiscono ciò che si potrebbe pensare come array in altre lingue.

Data questa differenza, ci sono situazioni specifiche in cui scegliere uno piuttosto che l’altro. Se sei nuovo in Go, determinare quando usarli può essere confuso: anche se la versatilità degli slice la rende una scelta più appropriata in molte situazioni, ci sono istanze in cui gli array possono ottimizzare il rendimento del tuo programma.

Questo articolo tratterà in dettaglio gli array e le slice, fornendovi le informazioni necessarie per scegliere correttamente tra questi tipi di dati. Inoltre, revisionerai le modalità più comuni per dichiarare e lavorare con sia gli array che le slice. Il tutorial prima descriverà gli array e come manipolarli, poi spiegherà le slice e le loro differenze.

Gli Array

Gli array sono strutture di dati di collezione con un numero fisso di elementi. Poiché la dimensione di un array è statica, la struttura dati ha bisogno di allocare memoria solo una volta, invece di una struttura a lunghezza variabile che deve allocare memoria in maniera dinamica così da potersi allargare o ridurre in futuro. Anche se la lunghezza fissa degli array può rendere il loro utilizzo un po ‘ rigido, l’allocazione memoria unica può aumentare la velocità e il rendimento del programma. A causa di ciò, i developer solitamente usano gli array quando si ottimizza i programmi in casi in cui la struttura dati non avrà mai un numero di elementi variabile.

Dichiarazione di un Array

Gli array sono definiti dichiarando la dimensione dell’array in parentesi quadre [ ], seguiti dal tipo di dati degli elementi. Un array in Go deve avere tutti gli elementi della stessa data type. Dopo il tipo di dati, è possibile dichiarare i valori individuali degli elementi dell’array in parentesi graffe { }.

Il seguente è lo schema generale per la dichiarazione di un array:

[capacity]data_type{element_values}

Nota: È importante ricordare che ogni dichiarazione di un nuovo array crea un tipo distinto. Quindi, sebbene [2]int e [3]int entrambi abbiano elementi interi, la loro lunghezza differisce, rendendo i loro tipi di dati incompatibili.

Se non si dichiara il valore degli elementi dell’array, il default è valorizzato a zero, il che significa che gli elementi dell’array saranno vuoti. Per gli interi, questo è rappresentato da 0, e per le stringhe questo è rappresentato da una stringa vuota.

Per esempio, l’array numbers seguente ha tre elementi interi che non hanno ancora un valore:

var numbers [3]int

Se si stampasse numbers, si riceverebbe l’output seguente:

Output
[0 0 0]

Se si desidera assegnare i valori degli elementi all’atto della creazione dell’array, i valori vanno messi nelle parentesi graffe. Un array di stringhe con valori predefiniti si presenta così:

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

È possibile memorizzare un array in una variabile e stamparlo:

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

Eseguendo un programma con le righe precedenti si otterrebbe l’output seguente:

Output
[blue coral staghorn coral pillar coral elkhorn coral]

Notice che non c’è nessuna delimitazione tra gli elementi dell’array quando viene stampato, rendendo difficile capire dove un elemento termina e l’altro inizia. A causa di ciò, a volte è utile usare la funzione fmt.Printf invece, che può formattare le stringhe prima di stamparle sullo schermo. Fornire il verbo %q con questo comando per istruire la funzione a mettere le virgoletto intorno ai valori:

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

Questo risulterà nel seguente:

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

Ora ogni elemento è citato. Il verbo \n istruisce al formattatore di aggiungere un ritorno a capo alla fine.

Con un’idea generale di come dichiarare array e cosa consistono, ora puoi passare all’apprendimento di come specificare elementi in un array con un numero di indice.

Indicizzare Array (e Slices)

Ogni elemento in un array (e anche in una slice) può essere chiamato individualmente tramite indicizzazione. Ogni elemento corrisponde a un numero di indice, che è un valore di int che parte dalla numerazione dell’indice 0 e si conta fino in alto.

Utilizzeremo un array nei seguenti esempi, ma potresti usare anche una slice, poiché sono identiche nella loro indicizzazione.

Per l’array coral, la decomposizione dell’indice è simile a questo:

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

Il primo elemento è il valore “blue coral”, che inizia all’indice 0 e la sezione termina al numero di indice 3 con l’elemento “elkhorn coral”.

Dato che ogni elemento di una sezione o un array ha un numero di indice corrispondente, abbiamo la possibilità di accedere e manipolare le loro singole unità come farebbero con altri tipi di dati sequenziali.

Ora possiamo chiamare un elemento discreto della sezione indicando il suo numero di indice:

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

I numeri di indice per questa sezione si spaziano da 0-3, come mostrato nell’ultima tabella. Quindi per chiamare qualsiasi elemento individualmente, dovremmo riferire ai numeri di indice come questo:

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

Se chiamiamo l’array coral con un indice numerico maggiore di 3, sarà fuori scopo poiché non sarà valido:

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

Quando si indica un array o una sezione, non è consentito usare un numero negativo; facendo così si verificherà un errore:

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

Possiamo concatenare gli elementi delle stringhe in un array o in una sezione con altri valori stringa utilizzando il operatore +:

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

Ho concatenato l’elemento della stringa all’indice numero 0 con la stringa “Sammy loves “.

Con i numeri di indice che corrispondono agli elementi all’interno di un array o sezione, abbiamo la possibilità di accedere ad ogni elemento discretamente e lavorarci con quegli elementi. Per dimostrarlo, prossimamente vedremo come modificare un elemento a un certo indice.

Modificare Elementi

Possiamo usare l’indicizzazione per cambiare gli elementi all’interno di un array o di una slice impostando un elemento numerato per indice uguale ad un valore diverso. Ciò ci dà un maggior controllo sulle informazioni negli array e nelle slice, e ci permetterà di manipolare in modo programmatico gli elementi individuali.

Se vogliamo cambiare il valore stringa dell’elemento all’indice 1 dell’array coral da "staghorn coral" a "foliose coral", possiamo farlo così:

coral[1] = "foliose coral"

Ora, quando stampiamo coral, l’array sarà diverso:

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

Adesso che sappiamo come manipolare gli elementi individuali di un array o di una slice, guardiamo the due funzioni che ci daranno maggiore flessibilità quando si lavora con i tipi di dati di collezione.

Conteggio Elementi con len()

In Go, len() è una funzione integrata creata per aiutarti a lavorare con array e slice. Come per le stringhe, puoi calcolare la lunghezza di un array o di una slice usando len() e passando l’array o la slice come parametro.

Ad esempio, per capire quanti elementi ci sono nell’array coral, userai:

len(coral)

Se stampi la lunghezza dell’array coral, riceverai l’output seguente:

Output
4

Questo indica che la lunghezza dell’array coral è pari al tipo di dati int, che è corretto perché l’array coral ha quattro elementi:

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

Se crei un array di interi con più elementi, potresti usare anche la funzione len() su questo:

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

Questo risulterà nel seguente output:

Output
13

Tuttavia, questa funzione è particolarmente utile quando si desidera determinare quanti elementi sono presenti in grandi array.

Successivamente, verremo a capire come aggiungere un elemento ad una collezione dati e mostraremo come, a causa della lunghezza fissa degli array, aggiungere elementi alla fine di questi tipi di dati statici risulterà in un errore.

Aggiungendo Elementi con append()

append() è un metodo predefinito in Go che aggiunge elementi a una collezione dati. Tuttavia, questo metodo non funzionerà quando viene utilizzato con un array. Come spiegato prima, il principale modo in cui gli array differiscono dai slice è che la dimensione di un array non può essere modificata dopo che è stata definita. Ci significa che se puoi cambiare i valori degli elementi all’interno di un array, non puoi farlo diventare più grande o più piccolo dopo che è stato definito.

Considerate il tuo array coral:

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

Vogliamo aggiungere l’elemento "black coral" a questo array. Se provate a usare la funzione append() con l’array digitando:

coral = append(coral, "black coral")

Riceverete un errore come output:

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

Per correggere questo, impareremo di più sul tipo di dato slice, come definire una slice e come convertire un array in una slice.

Slices

Un slice è un tipo di dato in Go che è una mutable, o cambiabile, sequenza ordinata di elementi. Poiché la dimensione di una slice è variabile, ci sono molte più flessibilità quando le usate; quando si lavora con collezioni di dati che potrebbero bisogno di ampliarsi o contrarre in futuro, l’uso di una slice garantirà che il tuo codice non si imbatti in errori quando si tenta di manipolare la lunghezza della collezione. In gran parte, questa mutabilità è giustificata dalla possibile riallocazione della memoria richiesta occasionalmente da slice quando si confrontano con gli array. Quando hai bisogno di memorizzare molti elementi o iterare su elementi e vuoi essere in grado di modificare facilmente quegli elementi, probabilmente vuoi lavorare con il tipo di dato slice.

Defining a Slice

Le slice sono definite dichiarando il tipo di dati preceduto da un set vuoto di parentesi quadre ([]) e una lista di elementi tra le parentesi curli ({}). Notare che, rispetto agli array che richiedono un int tra le parentesi per definire un determinato numero di elementi, la slice non ha nulla tra le parentesi, rappresentando la sua lunghezza variabile.

Possiamo ora creare una slice che contenga elementi del tipo stringa:

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

Quando stampiamo la slice, notiamo che gli elementi che sono nell’array sono:

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

Questo sarà risultato nel seguente:

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

Se vuoi creare una slice di un certo numero di elementi senza popolare ancora i valori della collezione, puoi usare la funzione predefinita make():

oceans := make([]string, 3)

Se fosse stato stampato questo slice, avreste ricevuto:

Output
["" "" ""]

Se vuoi allocare la memoria per una capacità specificata, puoi passare un terzo argomento alla funzione make():

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

Cosa farebbe una slice con lunghezza di 3 e una capacità preallocata di 5 elementi.

Ora sappiamo come dichiarare una slice. Tuttavia, questo non risolve ancora l’errore che abbiamo avuto con l’array coral prima. Per usare la funzione append() con coral, dovrai prima imparare come tagliare sezioni di array.

Tagliare Array in Slice

Utilizzando i numeri di indice per determinare gli endpoint, puoi chiamare una sezione subordinata delle valeurs all’interno di un array. Questo è chiamato slicing l’array e puoi fare questo creando un range di numeri di indice separati da una colonna, nella forma di [primo_indice:secondo_indice].È importante notare che, quando si taglia un array, il risultato è una sottovariabile, non un array.

Diciamo che vuoi stampare solo le voci intermedie dell’array coral, senza l’elemento prima e l’ultimo. Puoi farlo createndo una sottovariabile che inizia al numero 1 e finisce appena prima del numero 3:

fmt.Println(coral[1:3])

Eseguire un programma con questa linea mostrera il seguente:

Output
[foliose coral pillar coral]

Quando si crea una sottovariabile, come in [1:3], il primo numero è il punto di partenza (inclusivo), e il secondo numero è la somma del primo numero e del totale degli elementi che desideri recuperare:

array[starting_index : (starting_index + length_of_slice)]

Nel caso specifico, hai chiamato il secondo elemento (o indice 1) come punto di partenza e hai chiesto due elementi in totale. Così facendo, questo è come calcolare:

array[1 : (1 + 2)]

Che è come arrivare a questa notazione:

coral[1:3]

Se vuoi imprimerli i primi tre elementi dell’array coral — che saranno "blue coral", "foliose coral", e "pillar coral" — puoi fare così:

fmt.Println(coral[:3])

Questo ti stampera:

Output
[blue coral foliose coral pillar coral]

Questo ha stampato l’inizio dell’array, fermandosi appena prima dell’indice 3.

Per includere tutti gli elementi alla fine di un array, usare la sintassi di inversione:

fmt.Println(coral[1:])

Questo avrebbe dato il seguente intervallo:

Output
[foliose coral pillar coral elkhorn coral]

Questa sezione ha discusso su come chiamare singoli elementi di un array tramite segmentazione. Successivamente, imparare come convertire interi array in segmenti.

Conversione da un Array ad uno Slice

Se crei un array e decidi che hai bisogno di una lunghezza variabile, puoi convertirlo in un segmento. Per convertire un array in un segmento, usa il processo di segmentazione che hai imparato nell’istruzione Segmentazione degli Array. Ma questa volta selezionate tutto il segmento ommettendo entrambi i numeri che determinano le estremità:

coral[:]

Ricorda che non puoi convertire la variabile coral in un segmento stesso, poiché una volta definita in Go, il tipo di variabile non può essere cambiato. Per evitare questo problema, potresti copiare tutti i contenuti dell’array in una nuova variabile come segmento:

coralSlice := coral[:]

Se fosse stato stampato coralSlice, riceverete il seguente output:

Output
[blue coral foliose coral pillar coral elkhorn coral]

Ora tenta di aggiungere l’elemento black coral come nell’sezione array, usando append() con il nuovo convertito segmento:

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

Questo sarà stampato il segmento con l’elemento aggiunto:

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

Potremmo aggiungere anche più di un elemento in una singola istruzione append():

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

Per combinare due slice insieme, puoi usare append(), ma devi estendere il secondo argomento da aggiungere usando la sintassi di espansione ...:

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

Ora che hai imparato come aggiungere un elemento alla tua slice, guarderemo come rimuoverne uno.

Rimuovere un Elemento da una Slice

A differenza di altre lingue, Go non fornisce alcuna funzione interna per rimuovere un elemento da una slice. Gli elementi devono essere rimossi dalla slice tagliandoli.

Per rimuovere un elemento, devi tagliare gli elementi prima di quello e dopo di esso, poi appendere questi due nuovi slice insieme senza l’elemento che vuoi rimuovere.

Se i è l’indice dell’elemento da rimuovere, allora il formato di questo processo sarà simile al seguente:

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

Da coralSlice, rimuoviamo l’elemento "elkhorn coral". Questo elemento si trova all’indice posizione 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"]

Ora l’elemento all’indice posizione 3, la stringa "elkhorn coral", non è più nella nostra slice coralSlice.

Potrebbe anche eliminare un intervallo con la stessa approccia. Supponiamo che vogliamo rimuovere non solo l’elemento "elkorn corallo", ma anche "nero corallo" e "antipathes". Possiamo usare una espressione di range per fare questo:

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)

Questa espressione si comporterà come se fosse stato utilizzato un range nell’espressione:

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

Ora che sappiamo come aggiungere e rimuovere elementi da una slice, guardiamo ora come misurare quanto dati può contenere una slice al momento attuale.

Misura della capacità di una slice con cap()

Dato che le slice possono avere lunghezze variabili, il metodo len() non è l’opzione migliore per determinare la dimensione di questo tipo di dato. Invece, puoi usare la funzione cap() per sapere quanti elementi può contenere una slice. Questo mostrera ai tuoi risultati quanto memoria è già stata allocata per la slice.

Nota: Perché la lunghezza e la capacità di un array sono sempre uguali, la funzione cap() non funzionerà su array.

Un uso comune per cap() è quello di creare una slice con un numero predefinito di elementi e poi riempirli elementi programmaticamente. Questo evita potenzialiallocazioni inutili che potrebbero accadere se usassi append() per aggiungere elementi oltre la capacità attualmente allocata.

Il caso di esempio che vogliamo considerare è quello in cui vogliamo creare un elenco di numeri da 0 fino a 3. Possiamo usare append() in un ciclo per fare così, o possiamo pre-allocare la sezione prima e usare cap() per looping attraverso i valori da riempire.

Prima, guardiamo l’esempio di utilizzo di append():

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

Nell’esempio precedente abbiamo creato una sezione e poi abbiamo creato un for loop che avrebbe iterato quattro volte. Ogni iterazione ha aggiunto il valore corrente del variabile loop i nell’indice della sezione numbers. Tuttavia, questo potrebbe portare ad allocazioni di memoria innecessarie che rallentano il tuo programma. Quando si aggiungono elementi a una sezione vuota, ogni volta che fai un’appendice, il programma verifica la capacità della sezione. Se l’elemento aggiunto fa superare questa capacità, il programma allocera ulteriore memoria per compensarne. Questo comportamento può causare un carico di lavoro addizionale nel tuo programma e può risultare in un’esecuzione più lenta.

Ora popoliamo la sezione senza usare append() facendo pre-allocare una certa lunghezza/capacità:

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

fmt.Println(numbers)

Output
[0 1 2 3]

Nel nostro esempio abbiamo usato make() per creare una sezione e abbiamo pre-allocato 4 elementi. Abbiamo poi usato la funzione cap() nel loop per iterare su ogni elemento zeroizzato, riempiendolo fino alla capacità pre-allocata. N ogni loop, abbiamo inserito il valore corrente del variabile loop i nella posizione della sezione numbers.

Mentre le funzioni append() e cap() sono entrambe funzionalmente equivalenti, l’esempio di cap() evita ogni allocazione di memoria aggiuntiva che sarebbero state necessarie usando la funzione append().

Slicing Multidimensionale

Puoi anche definire tagli che consistono in altri tagli come elementi, con ogni elenco racchiuso all’interno delle parentesi maggiori del taglio padre. Le collezioni di questo tipo si chiamano tagli multidimensionali. Questi possono essere considerati come rappresentazioni delle coordinate multidimensionali; ad esempio, una raccolta di cinque tagli ognuno lungo sei elementi potrebbe rappresentare un griglia bidimensionale con lunghezza orizzontale di cinque e altezza verticale di sei.

Esaminiamo il seguente taglio multidimensionale:

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

Per accedere ad un elemento all’interno di questo taglio, dovremmo usare indici multipli, uno per ogni dimensione della costruzione:

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

Nel codice precedente, prima identifica l’elemento all’indice 0 del taglio all’indice 1, poi indica l’elemento all’indice 0 del taglio all’indice 0. Questo avverra il seguente risultato:

Output
Sammy shark

I valori degli indici per gli altri elementi singoli sono i seguenti:

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"

Quando si lavora con sottili slice multidimensionali, è importante tenere a mente che sarà necessario riferirsi a più di un numero di indice per accedere agli elementi specifici all’interno del slice nested relativo.

Conclusione

In questo tutorial, hai imparato i fondamenti del lavoro con array e slice in Go. Hai attraversato molti esercizi per dimostrare come gli array hanno una lunghezza fissa, mentre le slice hanno una lunghezza variabile, e hai scoperto come questa differenza influenza l’utilizzo delle strutture dati in determinate situazioni.

Per continuare a studiare le strutture dati in Go, consulta il nostro articolo su Comprendere le Mappe in Go, o esplora l’intera serie Come Programmare in Go.

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