Lezione 2 di 8 · 18 min di lettura
Slice, finestre su un array
Guardare un pezzo di array senza copiarlo. Il tipo int[], gli intervalli con .. e :, gli indici contati dalla fine con ^, e perché modificare una slice modifica l'array.
Una finestra, non una copia
Hai un array con le temperature di sei ore del giorno e ti interessano solo le prime tre, quelle del mattino. Potresti copiarle in un nuovo array int[3]. Ma spesso non vuoi una copia: vuoi solo guardare una parte dell’array che hai già.
È quello che fa una slice (in inglese, “fetta”): una finestra aperta su un pezzo di array. Il suo tipo si scrive come quello di un array, ma senza numero tra le quadre:
int[6] temps = { 12, 15, 19, 22, 18, 14 };
int[] all = &temps; // a slice looking at the whole array
io::printn(all);
io::printn(all.len);[12, 15, 19, 22, 18, 14]
6int[] si legge “una slice di int”: non dice quanti sono, perché una slice può guardare qualsiasi numero di elementi. Lo sa lei, e te lo dice con .len. La & davanti a temps significa “l’array vero e proprio, non una sua copia”: è il segno che la slice non si porta dietro i valori, ma indica dove stanno. Si usa e si scorre esattamente come un array: all[0], all.len, foreach (t : all).
Dettagli nerd Cosa c'è dentro una slice?
Una slice è piccolissima: sono due numeri. Il primo è l’indirizzo in memoria del suo primo elemento, cioè il numero della casella di memoria dove comincia (la RAM è una fila enorme di byte numerati, e l’indirizzo è semplicemente il numero di uno di quei byte). Il secondo è la lunghezza. In tutto 16 byte su un computer a 64 bit, che la slice guardi 3 elementi o 3 milioni.
Un indirizzo di memoria si chiama anche puntatore, perché “punta” a un dato. I puntatori avranno un modulo tutto loro; per ora ti basta sapere che una slice è un puntatore con la sua lunghezza sempre accanto. In C, la lunghezza la devi portare in giro tu, a parte, e se ti sbagli nessuno te lo dice.
Tagliare: gli intervalli
Il bello delle slice è che puoi aprire la finestra su un pezzo qualsiasi. Ci sono due modi di scrivere l’intervallo tra le quadre:
inizio..fine: dall’indiceinizioall’indicefine, entrambi inclusi;inizio:quanti: dall’indiceinizio, prendiquantielementi.
io::printn(temps[1..3]); // indexes 1, 2 and 3
io::printn(temps[1:3]); // three elements starting at index 1[15, 19, 22]
[15, 19, 22]Stesso risultato, due modi di pensare: con .. dici dove finisce, con : quanto è lunga. Usa quello che rende più chiaro il tuo codice.
Puoi anche omettere un estremo: senza inizio si parte da 0, senza fine si arriva in fondo.
io::printn(temps[..2]); // from the start up to index 2
io::printn(temps[3..]); // from index 3 to the end
io::printn(temps[..]); // everything[12, 15, 19]
[22, 18, 14]
[12, 15, 19, 22, 18, 14]temps[..] è l’intero array come slice: equivale a &temps.
Contare dalla fine: ^
Spesso interessano gli ultimi elementi. Invece di calcolare temps.len - 2, puoi scrivere ^2: il simbolo ^ significa “contando dalla fine”, e ^n vale len - n.
io::printn(temps[^2..]); // the last two
io::printn(temps[^1]); // the last one[18, 14]
14^1 è l’ultimo elemento, ^2 il penultimo, e così via. Funziona anche come indice semplice, fuori da un intervallo.
Quiz
Con int[6] temps = { 12, 15, 19, 22, 18, 14 };, cosa contiene temps[2:3]?
Guardare è toccare
Una slice non ha valori suoi: guarda quelli dell’array. Quindi se scrivi attraverso la slice, modifichi l’array:
int[] morning = temps[0:3];
morning[0] = -5;
io::printn(temps);[-5, 15, 19, 22, 18, 14]È la differenza fondamentale con l’array della lezione precedente: int[3] b = a; fa una copia, int[] s = a[0:3]; apre una finestra. Stessa sintassi per leggere e scrivere, effetti molto diversi.
Puoi anche tagliare una slice, e ottenere una finestra più piccola sullo stesso array:
int[] middle = temps[1..4]; // 15, 19, 22, 18
int[] inner = middle[1..2]; // 19, 22: indexes are relative to middle!
inner[0] = 0;
io::printn(temps);[-5, 15, 0, 22, 18, 14]Attenzione agli indici: middle[1..2] conta dall’inizio di middle, non dell’array. Ma la memoria sotto è sempre quella di temps.
Riempire e copiare a pezzi
Assegnando un singolo valore a una slice, lo scrivi in tutte le sue caselle:
temps[0..2] = 20;
io::printn(temps);[20, 20, 20, 22, 18, 14]E assegnando una slice a un’altra della stessa lunghezza, copi gli elementi da una all’altra:
int[3] backup = { 7, 8, 9 };
temps[3..] = backup[..];
io::printn(temps);[20, 20, 20, 7, 8, 9]Le due slice devono avere la stessa lunghezza: qui entrambe ne hanno 3.
Infine, == tra due slice confronta il contenuto, come per gli array:
int[*] x = { 1, 2, 3 };
int[*] y = { 9, 2, 3 };
io::printn(x[1..] == y[1..]); // [2, 3] == [2, 3]trueFuori dalla finestra
Una slice non può uscire dall’array che guarda. Se l’intervallo sfora, il programma si ferma, come con gli indici:
import std::io;
fn void main()
{
int[6] temps = { 12, 15, 19, 22, 18, 14 };
int start = 4;
io::printn(temps[start:3]);
}ERROR: 'End index out of bounds (end index of 6 exceeds size of 6)'
in window.main (window.c3:7) [window] [inline]Tre elementi a partire dall’indice 4 sarebbero gli indici 4, 5 e 6, ma l’ultimo indice valido è 5. Il messaggio parla di “end index of 6”: l’ultimo indice che hai chiesto.
Esercizio · sul tuo computer
Il contapassi
Scrivi steps.c3 con i passi di una settimana, da lunedì a domenica:
int[*] week = { 8200, 10400, 6300, 12000, 9100, 15500, 4800 };Senza copiare nulla, crea tre slice e stampale:
workdays: da lunedì a venerdì, usando un intervallo senza inizio;weekend: gli ultimi due giorni, usando^;midweek: da martedì a giovedì, usando la formainizio:quanti.
Poi:
- stampa
Last day:e l’ultimo elemento diweek, usando^; - azzera la domenica scrivendo attraverso
weekend(non attraversoweek!) e stampaAfter reset:con l’array intero; - con una sola assegnazione, metti 10000 da martedì a giovedì e stampa
Flattened:con l’array intero.
Mostra una soluzione (prima prova da solo!)
import std::io;
fn void main()
{
int[*] week = { 8200, 10400, 6300, 12000, 9100, 15500, 4800 };
int[] workdays = week[..4];
int[] weekend = week[^2..];
int[] midweek = week[1:3];
io::printfn("Workdays: %s", workdays);
io::printfn("Weekend: %s", weekend);
io::printfn("Midweek: %s", midweek);
io::printfn("Last day: %d", week[^1]);
weekend[1] = 0;
io::printfn("After reset: %s", week);
week[1..3] = 10000;
io::printfn("Flattened: %s", week);
}Il punto 5 è quello che conta: weekend[1] è la seconda casella della finestra, cioè la domenica di week. Hai cambiato l’array senza mai nominarlo. Se weekend fosse stato un int[2] (una copia), week sarebbe rimasto com’era.
Ricapitolando
- Una slice
int[]è una finestra su un array: non ha valori suoi, e ha sempre la sua.len. &arrearr[..]danno l’array intero come slice.a[1..3]va dall’indice 1 al 3 inclusi;a[1:3]prende 3 elementi dall’indice 1. Gli estremi si possono omettere.^nconta dalla fine:a[^1]è l’ultimo elemento,a[^2..]gli ultimi due.- Scrivere in una slice modifica l’array sotto.
a[0..2] = 0;riempie,a[3..] = b[..];copia. ==confronta il contenuto; uscire dai limiti ferma il programma.
Ora che sai cos’è una slice, puoi risolvere il problema lasciato in sospeso: scrivere funzioni che accettano array di qualsiasi lunghezza.