venerdì 27 marzo 2015

Corso Lua - puntata 4 - Costrutti di base


Hello world!

Come tutti i linguaggi di scripting non occorre compilare il codice Lua. Basta scrivere il programma in un file di testo --- preferibilmente assegnandogli l'estensione .lua --- ed eseguirlo con il comando da terminale $ lua nomefile.

Per scrivere il codice vi consiglio di utilizzare l'editor SciTE perché è già predisposto per eseguire programmi Lua ed è programmabile in Lua. Tra l'altro l'output del programma viene visualizzato nella output tab che funge da comodo terminale. Il tasto funzione F5 esegue il programma visualizzato nella scheda attiva in quel momento e il tasto F8 attiva/diasttiva la finestra di output che è un terminale incorporato nell'editor.

Prepariamoci a scrivere il primo programma in Lua, ovviamente il fatidico 'Hello world!'.
Salviamo il seguente codice in un file chiamato "primo.lua" ed eseguiamolo direttamente in SciTE premendo il tasto funzione F5 o al terminale con il comando $ lua primo.lua:

  print("Hello world!")


In ambiente Linux o Mac OS X come per tutti gli altri linguaggi di scripting possiamo inserire come prima riga la sha-bang. Dando i permessi di esecuzione al file possiamo eseguire il programma semplicemente scrivendone il nome al terminale, e in questo caso è conveniente omettere l'estensione.
Ecco il programma:
#!/usr/bin/env lua

print("Hello world!")

Lua è in grado di passare al programma gli argomenti che l'utente digita separandoli da spazi nella linea di comando, tramite la tabella array chiamata 'arg'. arg[0] conterrà il nome dello script mentre arg[1], arg[2], eccetera conterrà i vari argomenti utente convertiti nel tipo stringa. Questa proprietà assieme alla tecnica dello sha-bang da all'utente la possibilità di scrivere programmi in Lua come se fossero comandi da terminale, almeno per gli ambienti di classe *nix.

Primo problema: il ciclo for

Va bene, ma siamo qui per scrivere codice e allora cominciamo con il voler contare i numeri pari contenuti in una tabella che funziona come un array --- ricordandoci che gli indici partono da 1 e non da 0.
Rileggete le prime tre puntate del corso come utile riferimento.

Cominciamo creando la tabella con il costruttore in linea per poi iterare con un ciclo for.
-- costruttore (l'ultima virgola è opzionale)
-- i doppi trattini rappresentano un commento
-- come // lo sono per il C
local t = {12, 45, 67, 101,   3,
            2, 89, 36,   7,  99,
           88, 33, 17,  12, 203,
           46,  1, 19,  50, 456}

local c = 0 -- contatore
for i = 1, #t do
    if t[i] % 2 == 0 then
        c = c + 1
    end
end

print(c)

Il corpo del ciclo 'for' di Lua è il blocco compreso tra le parole chiave obbligatorie 'do' ed 'end'. La variabile 'i' interna al ciclo assumerà i valori da 1 al numero di elementi della tabella ottenuti con l'operatore #.

Per ciascuna iterazione con il costrutto condizionale 'if' incrementeremo un contatore solo nel caso che l'elemento della tabella è pari. L'if ha anch'esso bisogno di definire il blocco e lo fa con le parole chiavi obbligatorie 'then' e 'end'.

Ma come si comporta l'operatore di lughezza # per le tabelle array con indici non lineari? Per esempio, qual è il risultato del seguente codice:
 local t = {}
 t[1] = 1
 t[2] = 2
 t[1000] = 3

 print(#t)

Mentre con questo cosa verrà stampato?
 local t = {}
 t[1000] = 123

 print(#t)

Avrete certamente capito che l'operatore # tiene conto solamente degli elementi con indici a cominciare da 1 che proseguono senza 'buchi' di 1 in 1. Infatti, l'operatore di lunghezza # considera il valore nil come termine della tabella, ma è usato molto spesso per inserire progressivamene elementi in una tabella:
local t = {}
for i = 1,100 do
    t[#t+1] = i*i
end

print(t[100])

Secondo problema: il ciclo while

Passiamo a scrivere il codice per inserire in una tabella i fattori primi di un numero. Fatelo per esercizio e poi confrontate il codice seguente che utilizza l'operatore modulo % (resto della divisione intera):
local factors = {}
local n = 123456789

local div = 2
while n > 1 do
    if n % div == 0 then
        factors[#factors + 1] = div
        n = n / div
        while n % div == 0 do
            n = n / div
        end
    end
    div = div + 1
end

for i= 1, #factors do
    print(factors[i])
end

Così abbiamo introdotto anche il ciclo 'while' perfettamente coerente con la sintassi dei costrutti visti fino a ora: il blocco ripetuto fino a che la condizione è vera è obbligatoriamente definito da due parole chiave, quella di inizio è 'do' e quella di fine è 'end'.

Intermezzo

Siamo all'intervallo!
Non ci sarà pubblicità ma ulteriori notizie su Lua!
In Lua non è obbligatorio inserire un carattere delimitatore sintattico ma è facoltativo il ';'.
I caratteri spazio, tabulazione e ritorno a capo vengono considerati dalla grammatica come separatori, perciò si è liberi di formare il codice con identazione o anche più istruzioni sulla stessa linea.
Solitamente non si utilizzano i punti e virgola finali, ma se ci sono due assegnazioni sulla stessa linea --- stile sconsigliabile perché brutto --- li si può separare con un ';'.
Come sempre una forma stilistica chiara e semplice vi aiuterà a scrivere codice più leggibile e semplice da comprendere a distanza di tempo.

Generalmente è buona norma definire le nuove variabili più vicino possibile al punto in cui verranno utilizzate per la prima volta, un beneficio per la comprensione ma anche per la correttezza del codice perché forse eviterà di fare confusione con i nomi e magari introdurre errori.
Ok. Intervallo finito...

Terzo problema: il ciclo for con step

Altro tema: verificare se un numero è palindromo ovvero che gode della proprietà che le cifre decimali sono simmetriche, come per esempio avviene per il numero 123321. Prima provate a scrivere il codice Lua per conto vostro e poi confrontate questa soluzione:
local digit = {}
local n = 123321

local num = n
while num > 0 do
    digit[#digit + 1 ] = num % 10
    num = (num - num % 10) / 10
end

local sym_n, dec = 0, 1
for i=#digit,1,-1 do
    sym_n = sym_n + digit[i]*dec
    dec = dec * 10
end

print(sym_n == n)

La soluzione utilizza una tabella per memorizzare le cifre del numero che vengono poi utilizzate nel ciclo for a partire dall'ultima (la cifra più significativa) fino alla prima per ricalcolare il numero con le cifre invertite. Per esempio se il numero fosse 123 l'algoritmo restituirebbe 321. Se il numero iniziale è palindromo allora il corrispondente numero a cifre invertite è uguale al numero di partenza.

Nel ciclo for il terzo parametro opzionale -1 imposta il passo per la variabile i che quindi passa dal numero di cifre del numero da controllare (6 nel nostro caso) ad 1.

In effetti non è necessaria la tabella:
local n = 123321

local num, sym_n, dec = n, 0, 1
while num > 0 do
    sym_n = sym_n + (num % 10)*dec
    dec = 10 * dec
    num = (num - num % 10) / 10
end

print(sym_n == n)

Quarto problema: if a rami multipli

Il prossimo problema è il seguente: determinare il numero di cifre di un intero. Confrontate ancora una volta il codice proposto solo dopo aver cercato una vostra soluzione.
local n = 786478654
local digits
if n < 10 then
    digits = 1 -- attenzione non 'local digits = 1'
elseif n < 100 then
    digits = 2
elseif n < 1000 then
    digits = 3
elseif n < 10000 then
    digits = 4
elseif n < 100000 then
    digits = 5
elseif n < 1000000 then
    digits = 6
elseif n < 10000000 then
    digits = 7
elseif n < 100000000 then
    digits = 8
elseif n < 1000000000 then
    digits = 9
elseif n < 10000000000 then
    digits = 10
else -- fermiamoci qui...
    digits = 11
end

print(digits)

Questo esempio mostra in azione l'if a più rami che in Lua svolge la funzione del costrutto 'switch' presente in altri linguaggi, che introduce una nuova parola chiave: 'elseif'.
L'esempio è interessante anche per come viene introdotta la variabile 'digits', cioè senza inizializzarla per poi assegnarla nel ramo opportuno dell'if.
Infatti una variabile interna a un blocco non sopravvive oltre esso, quindi dichiararla all'interno dell'if non è sufficiente.

Attenzione a non premettere 'local' nelle assegnazioni dei rami del condizionale: in questo caso verrebbe creata una nuova variabile locale al blocco con lo stesso nome della variabile esterna che viene oscurata. In altri termini, al termine del blocco 'digits' varrebbe ancora nil perché avremmo modificato una variabile locale al blocco.

Esercizi

Contare quanti interi sono divisibili sia per 2 che per 3 nell'intervallo [1, 10000]. Suggerimento:utilizzare l'operatore modulo '%' resto della divisione intera tra due operandi.

Determinare i fattori del numero intero 5461683.

Instanziare la tabella seguente di tre tabelle/array di tre numeri e calcolarne il determinante.
 t = {
    { 0,  5, -1},
    { 2, -2,  0},
    {-1,  0,  1},
}

Data la tabella seguente stampare in console il conteggio dei numeri pari e dei numeri dispari contenuti in essa. Verificare con un costrutto 'assert()' che la somma di questi due conteggi sia uguale alla dimensione della tabella.
 t = {
    45, 23, 56, 88, 96, 11,
    80, 32, 22, 85, 50, 10,
    32, 75, 10, 66, 55, 30,
    10, 13, 23, 91, 54, 19,
    50, 17, 91, 44, 92, 66,
    71, 25, 19, 80, 17, 21,
    81, 60, 39, 15, 18, 28,
    23, 10, 18, 30, 50, 11,
    50, 88, 28, 66, 13, 54,
    91, 25, 23, 17, 88, 90,
    85, 99, 22, 91, 40, 80,
    56, 62, 81, 71, 33, 30,
    90, 22, 80, 58, 42, 10,
}

Data la tabella precedente scrivere il codice per costruire una seconda tabella uguale alla prima ma priva di duplicati senza alterare l'ordine delle cifre.

Data la tabella precedente costruire una tabella le cui chiavi siano i numeri contenuti in essa e i valori siano il corrispondente numero di volte che la chiave stessa compare nella tabella di partenza. Stampare poi in console il numero che si presenta il maggior numero di volte.

Esempio spaziale

Data la tabella Lua riportata nel seguito con dati sugli orbiter del programma Space Shuttle della Nasa, scrivere un programma per calcolare:
  • il numero totale dei voli effettuati dagli orbiter;
  • il numero totale degli attracchi alla Stazione Spaziale Internazionale (ISS);
  • il tempo medio di costruzione in anni degli orbiter (è consentito assumere i mesi pari a 30 giorni);
  • per ciascun orbiter, il tempo in giorni trascorso tra la prima missione e l'ultima;
  • il nome dell'orbiter che a volato per primo.

Un quiz articolato (mentre scrivevo le domande mi sembrava di essere a Rischiatutto con Mike Buongiorno).

-- Space Shuttle Missions Database. Version 1.8.
-- from: https://sites.google.com/site/monzitrek/missioni-shuttle
-- by: © 2005-2011 - Ing. Luigi Morielli
-- licence: http://creativecommons.org/licenses/by-nc-sa/2.5/it/

-- per info consultare anche i siti dell'associazione ISAA:
-- http://www.isaa.it/
-- http://www.forumastronautico.it/
-- http://www.astronauticast.com/

Shuttle_DB = {-- tables' array
    {
        orbiter_id = "OV-099", orbiter_name ="Challenger",
        flight = 10,
        ISS_docking =  0,
        start_costruction = {d=24, m=06, y=1975},
        end_costruction = {d=23, m=10, y=1981},
        first_mission = {d=04, m=04, y=1983},
        last_mission = {d=28, m=01, y=1986},
        cause = "destroyed",
    },
    {
        orbiter_id = "OV-102", orbiter_name ="Columbia",
        flight = 28,
        ISS_docking =  0,
        start_costruction = {d=27, m=03, y=1975},
        end_costruction = {d=08, m=03, y=1979},
        first_mission = {d=12, m=04, y=1981},
        last_mission = {d=01, m=02, y=2003},
        cause = "destroyed",
    },
    {
        orbiter_id = "OV-103", orbiter_name ="Discovery",
        flight = 39,
        ISS_docking = 13,
        start_costruction = {d=27, m=08, y=1979},
        end_costruction = {d=25, m=02, y=1983},
        first_mission = {d=30, m=08, y=1984},
        last_mission = {d=24, m=02, y=2011},
        cause = "retired",
    },
    {
        orbiter_id = "OV-104", orbiter_name ="Atlantis",
        flight = 33,
        ISS_docking = 12,
        start_costruction = {d=03, m=03, y=1980},
        end_costruction = {d=10, m=04, y=1984},
        first_mission = {d=03, m=10, y=1985},
        last_mission = {d=08, m=07, y=2011},
        cause = "retired",
    },
    {
        orbiter_id = "OV-105", orbiter_name ="Endeavour",
        flight = 25,
        ISS_docking = 12,
        start_costruction = {d=15, m=02, y=1982},
        end_costruction = {d=06, m=07, y=1990},
        first_mission = {d=07, m=05, y=1992},
        last_mission = {d=16, m=05, y=2011},
        cause = "retired",
    },
}

Riassunto della puntata

Finalmente un po' di codice in Lua! Abbiamo incontrato le particolarità dell'operatore lunghezza '#' e i costrutti di base come i cicli 'for', 'while' e il condizionale 'if'.

Proseguiamo nella prossima puntata a presentare nuovi elementi del linguaggio attraverso esempi di codice. Sarà la volta degli operatori logici 'and', 'not' e 'or' e delle operazioni sulle stringhe.

Nessun commento:

Posta un commento