Lezione 6 di 6 · 18 min di lettura
Riepilogo e sfida finale
Il modulo sulle liste in una pagina, tre domande di controllo, e la macchina del resto riscritta con una lista di monete.
Guarda quanta strada
Cinque lezioni fa i tuoi valori erano tutti soli. Adesso sai:
- creare liste, aggiungere in testa, e perché in coda no;
- smontarle con i pattern
[],[x],[first, ..rest], con il compilatore che conta le forme mancanti; - percorrerle con la ricorsione, e costruirne di nuove, anche in coda con
list.reversealla fine; - trasformarle, filtrarle e ordinarle con
list.map,list.filter,list.sorte compagnia, tutto nel tubo; - ridurle a un valore solo con
list.fold.
La pagina da tenere accanto
| Cosa | Come si scrive |
|---|---|
| Una lista | [1, 2, 3], tipo List(Int); [] è vuota |
| Aggiungere in testa | [0, ..numbers] |
| Stampare una lista | string.inspect(numbers) |
| Lista vuota / con un elemento | [] -> ..., [x] -> ... |
| Il primo e il resto | [first, ..rest] -> ... |
| Almeno due elementi | [_, _, ..] -> ... |
| Nome a un sotto-pattern | [[_, ..] as found, ..] |
| Ricorsione su una lista | caso [], passo [first, ..rest] |
| Costruire in coda | loop(rest, [new, ..acc]), poi list.reverse(acc) |
| Trasformare | list.map(l, fn(x) { ... }) |
| Scegliere | list.filter(l, fn(x) { ... }) |
| Ridurre | list.fold(over: l, from: 0, with: fn(acc, x) { ... }) |
| Numerare | list.index_map(l, fn(x, i) { ... }) |
| Ordinare | list.sort(l, by: int.compare) |
| Contare, qualcuno, tutti | list.count, list.any, list.all |
| Testo e liste | string.split(s, on: " "), string.join(l, with: ", ") |
| Per ogni elemento | list.each(l, io.println) |
Trovi tutto anche nel Codex (tasto K).
Quiz di controllo
Quiz
Quanto vale list.map([1, 2, 3], fn(n) { n * n }) |> int.sum?
Quiz
Quale di queste clausole corrisponde alla lista [5]?
Quiz
Quanto vale list.fold(["a", "b"], "x", fn(acc, s) { acc <> s })?
La sfida: la macchina del resto, con le liste
Nella sfida del modulo 2 le monete erano messe in fila da una funzione, next_coin, che per ogni moneta diceva quale veniva dopo. Era un trucco: quella fila di monete è, in realtà, una lista. Riscriviamo la macchina del resto partendo proprio da lì.
Esercizio · sul tuo computer
La macchina del resto, versione 2
Nel progetto exercises crea src/change_list.gleam. Il prodotto costa 1315 centesimi e il cliente paga 2000, come prima. Le monete sono una costante:
const coins = [200, 100, 50, 20, 10, 5, 2, 1]Scrivi:
counts(amount: Int, coins: List(Int)) -> List(Int): quante monete servono di ogni tipo, nello stesso ordine della lista delle monete. Per 685 centesimi vale[3, 0, 1, 1, 1, 1, 0, 0]. È una ricorsione sulla lista delle monete: per la prima moneta ne servonoamount / coin, e per le altre si ricomincia daamount % coin;lines(coins: List(Int), counts: List(Int)) -> List(String): una riga come"3 x 2.00"per ogni moneta usata, saltando quelle con conteggio 0. Percorre le due liste insieme, con uncasesu due soggetti;format_cents, come sempre.
In main stampa il resto, le righe (con list.each) e il totale delle monete (con int.sum sui conteggi):
Change: 6.85
3 x 2.00
1 x 0.50
1 x 0.20
1 x 0.10
1 x 0.05
Coins: 7Suggerimento per lines: tre clausole. Tutte e due le liste hanno un primo elemento e il conteggio è 0; tutte e due hanno un primo elemento; tutto il resto (una delle due è finita).
Mostra una soluzione (prima prova da solo!)
import gleam/int
import gleam/io
import gleam/list
import gleam/string
const coins = [200, 100, 50, 20, 10, 5, 2, 1]
pub fn main() -> Nil {
let change = 2000 - 1315
let used = counts(change, coins)
io.println("Change: " <> format_cents(change))
lines(coins, used) |> list.each(io.println)
io.println("Coins: " <> int.to_string(int.sum(used)))
}
/// How many of each coin make up `amount`, largest coins first.
fn counts(amount: Int, coins: List(Int)) -> List(Int) {
case coins {
[] -> []
[coin, ..rest] -> [amount / coin, ..counts(amount % coin, rest)]
}
}
/// One line per coin actually used, like "3 x 2.00".
fn lines(coins: List(Int), counts: List(Int)) -> List(String) {
case coins, counts {
[_, ..other_coins], [0, ..other_counts] -> lines(other_coins, other_counts)
[coin, ..other_coins], [count, ..other_counts] -> [
int.to_string(count) <> " x " <> format_cents(coin),
..lines(other_coins, other_counts)
]
_, _ -> []
}
}
fn format_cents(cents: Int) -> String {
int.to_string(cents / 100)
<> "."
<> { cents % 100 |> int.to_string |> string.pad_start(2, "0") }
}Qualche dettaglio da notare:
- Il programma è diviso in tre passi indipendenti: calcolare i conteggi, trasformarli in righe, stampare. Ognuno si può provare da solo, e
countsnon stampa niente: restituisce solo dati. - Il totale delle monete non ha più bisogno di un accumulatore dedicato: è la somma dei conteggi.
countselinesnon sono in coda, e va bene così: le monete sono otto, e la forma “normale” è più facile da leggere.- Nella clausola dello zero la moneta non serve, quindi è
_; il conteggio invece deve essere proprio0.
Cosa succede nel Modulo 4
La macchina del resto ha un piccolo difetto: tiene le monete in una lista e i loro conteggi in un’altra, e tocca stare attenti a percorrerle insieme. Sarebbe più naturale avere una lista di coppie: moneta e conteggio, insieme. E più in generale: come si rappresenta un prodotto, che ha un nome, un prezzo e una quantità? O un semaforo, che può essere rosso, giallo o verde e nient’altro?
Nel prossimo modulo impareremo a costruire tipi nostri: le tuple per raggruppare valori al volo, i record con i campi con un nome, e i tipi personalizzati con le loro varianti, che con case diventano lo strumento più potente di tutto Gleam.