Lezione 8 di 8 · 22 min di lettura

Riepilogo e sfida finale

Tutto il modulo in una tabella, tre quiz di controllo e una sfida che legge un registro di punteggi, lo interpreta e disegna una classifica.

Dai valori alle collezioni

All’inizio del modulo sapevi tenere un valore per variabile, e un array visto di sfuggita. Adesso sai:

  • usare gli array fissi fino in fondo, anche a due dimensioni, sapendo che sono valori che si copiano;
  • guardarli a pezzi con le slice, senza copiare nulla, e scrivere funzioni che accettano collezioni di qualsiasi lunghezza;
  • trattare il testo come quello che è, una slice di byte: tagliarlo, confrontarlo, cercarci dentro, esaminarlo carattere per carattere;
  • costruire testo nuovo con tformat e DString, e raccogliere valori in una lista che cresce;
  • distinguere dove vivono i dati: letterali in sola lettura, variabili locali nello stack, memoria temporanea da pulire con @pool.

L’ultimo punto è quello che ti porterai dietro più a lungo. “Chi possiede questi byte, e fino a quando esistono?” è la domanda che separa chi programma in C3 (o in C) da chi usa linguaggi che se ne occupano da soli.

La pagina da tenere accanto

CosaCome si scrive
Array azzeratoint[5] a; (dimensione numero o costante)
Solo alcune caselleint[8] s = { [2] = 1, [5] = 1 };
Lunghezza e byte del tipoint[4]::len, int[4]::size
Copia e confrontoint[3] b = a; copia, a == b confronta il contenuto
Slice dell’intero arrayint[] s = &a; oppure a[..]
Intervallia[1..3] (fine inclusa), a[1:3] (inizio, quanti), a[..2], a[3..]
Dalla finea[^1] ultimo, a[^2..] ultimi due
Riempire / copiare a pezzia[0..2] = 0; / a[3..] = b[..];
Parametro collezionefn int sum(int[] values) chiamata con sum(&a)
Grigliaint[4][3] g; (3 righe da 4), g[riga][colonna]
Carattere di una stringas[0] è un char: si stampa con %c
Cercare nel testostarts_with, ends_with, contains, count
Ripuliretrim(), strip_prefix(x), strip_suffix(x)
Esaminare un caratterec.is_upper(), c.is_digit(), c.to_upper(), c - '0'
Testo modificabilechar[*] buf = "hello"; poi (String)&buf
Costruire testostring::tformat("%s: %d", name, n)
Spezzare / riunires.tsplit(",") / string::tjoin(parts, ", ")
CostruttoreDString t; t.append("x"); t.appendf("%d", n);
Pulizia della memoria temporanea@pool() { ... };
ListaList{int} l; l.push(3); l[0]; l.len();
Lista come slicel.array_view()
Ordinaresort::quicksort(&l);

Cinque errori che ormai riconosci

MessaggioCosa hai fatto
Too few (2) elements in initializer, expected 5.Hai elencato solo alcuni valori di un array (o hai invertito le dimensioni di una griglia)
Expected a constant value as length.La dimensione di un array fisso è una variabile: serve una costante, o una lista
Conversions from arrays or vectors to slices are only permitted on constant arrays...Hai passato un array a una funzione che vuole una slice senza &
Arrays of different lengths may not be converted.Hai assegnato o passato un array di lunghezza diversa da quella del tipo
End index out of bounds (end index of 6 exceeds size of 6)Un intervallo esce dall’array (mentre il programma gira)

E due errori che il compilatore non vede, e che ora sai riconoscere dai sintomi: numeri a caso da una slice che guarda un array locale ormai sparito, e ����� da una stringa temporanea usata dopo il suo @pool.

Quiz di controllo

Quiz

Cosa stampa? int[*] a = { 1, 2, 3, 4, 5 }; int[] s = a[1..3]; s[0] = 9; io::printn(a);

Quiz

Cosa stampa String w = "banana"; io::printn(w[1..3]);?

Quiz

Quale di questi usi della memoria è corretto?

La sfida: la classifica

Un programma che legge dati grezzi, li interpreta e presenta un risultato: è la forma di una quantità enorme di software vero. Qui i dati arrivano come un’unica stringa, un registro di partite con il nome del giocatore e i punti fatti.

Esercizio · sul tuo computer

La classifica

Scrivi scores.c3 partendo da questa costante:

c3
const String LOG = "ada:42,grace:17,linus:8,ada:30,barbara:55,grace:25,linus:12";

1. Da testo a numero. Scrivi fn int parse_number(String digits) che trasforma una stringa di cifre nel numero corrispondente, senza funzioni della libreria: scorri le cifre e, per ognuna, moltiplica per 10 quello che hai già e aggiungi il valore della cifra (ricordi c - '0'?). "42" diventa 4, poi 4 × 10 + 2 = 42.

2. Iniziale maiuscola. Scrivi fn String capitalize(String word) che restituisce la parola con l’iniziale maiuscola, usando string::tformat con i segnaposto %c e %s.

3. Sommare per giocatore. Spezza il registro nelle singole voci (separate da ,) e ogni voce in nome e punti (separati da :). Tieni due liste “parallele”: List{String} names e List{int} totals, dove totals[i] è il totale di names[i]. Per ogni voce cerca il nome in names: se c’è, aggiungi i punti al totale corrispondente; se non c’è, aggiungi il nome in fondo a names e i punti in fondo a totals.

4. La classifica. Stampa Entries: con il numero di voci e , players: con il numero di giocatori. Poi, per ogni giocatore nell’ordine in cui è comparso, una riga con il nome con l’iniziale maiuscola allineato a sinistra su 8 caratteri, il totale su 3, e una barra di #, uno ogni 10 punti interi (72 punti, 7 #), costruita con un DString. Infine Winner: con il giocatore con più punti, with, i punti e points.

L’output atteso, carattere per carattere:

output
Entries: 7, players: 4
Ada       72 #######
Grace     42 ####
Linus     20 ##
Barbara   55 #####
Winner: Ada with 72 points
Mostra una soluzione (prima prova da solo!)
scores.c3
import std::io;
import std::collections::list;

const String LOG = "ada:42,grace:17,linus:8,ada:30,barbara:55,grace:25,linus:12";

fn int parse_number(String digits)
{
	int value = 0;
	foreach (c : digits)
	{
		value = value * 10 + (c - '0');
	}
	return value;
}

fn String capitalize(String word)
{
	return string::tformat("%c%s", word[0].to_upper(), word[1..]);
}

fn void main()
{
	List{String} names;
	List{int} totals;

	String[] entries = LOG.tsplit(",");
	foreach (entry : entries)
	{
		String[] parts = entry.tsplit(":");
		String name = parts[0];
		int points = parse_number(parts[1]);

		bool found = false;
		for (sz i = 0; i < names.len(); i++)
		{
			if (names[i] == name)
			{
				totals[i] += points;
				found = true;
				break;
			}
		}
		if (!found)
		{
			names.push(name);
			totals.push(points);
		}
	}

	io::printfn("Entries: %d, players: %d", entries.len, names.len());

	sz best = 0;
	for (sz i = 0; i < names.len(); i++)
	{
		DString bar;
		for (int j = 0; j < totals[i] / 10; j++) bar.append('#');
		io::printfn("%-8s %3d %s", capitalize(names[i]), totals[i], bar);
		if (totals[i] > totals[best]) best = i;
	}

	io::printfn("Winner: %s with %d points", capitalize(names[best]), totals[best]);
}

I punti delicati:

  • parse_number: il trucco value * 10 + cifra “fa scorrere” a sinistra le cifre già lette. È lo stesso algoritmo che usano le funzioni di conversione della libreria (che in più gestiscono segni, spazi ed errori, come una stringa "4x2": la nostra versione si fiderebbe ciecamente).
  • Le liste parallele: funzionano finché le tieni allineate, cioè ogni push su una va accompagnato da un push sull’altra. È fragile, e nel prossimo modulo vedrai lo strumento giusto: una struct che tiene nome e totale insieme, in una sola List.
  • totals[i] += points: una lista si modifica per indice come un array, anche con gli operatori composti.
  • totals[i] / 10: divisione intera, quindi 72 diventa 7 e 55 diventa 5. Qui è proprio quello che vogliamo.
  • Niente free: tsplit, tformat, le liste e i DString usano la memoria temporanea, che se ne va con il programma.

Cosa succede nel Modulo 4

Le liste parallele della sfida ti hanno fatto sentire il limite: un giocatore è un nome e un punteggio, e tenerli in due collezioni separate è scomodo e rischioso. Nel prossimo modulo impari a creare tipi tuoi: le struct, che raccolgono valori diversi sotto un nome solo, e gli enum, per i valori che possono essere solo uno tra pochi (un seme delle carte, un giorno della settimana, lo stato di una partita). Con loro, i tuoi programmi cominceranno a descrivere il mondo con le sue parole.

Segna questa lezione come completata. Hai appena scritto un piccolo interprete di dati: non è poco.