venerdì 10 aprile 2015

Corso Lua - puntata 6


Funzioni

Le funzioni in Lua sono il principale mezzo di astrazione e lo strumento base per rendere il codice strutturato. Nelle prime puntate di questo corso base su Lua ne avete sentito la mancanza, dite la verità!

Coerentemente con il resto del linguaggio la sintassi di una funzione comprende due parole chiave che servono per delimitare il blocco di codice contenuto in essa: 'function' ed 'end'. Una funzione può accettare argomenti e può restituire dati tramite la parola chiave 'return'.

Come primo esempio, vi presento una funzione per calcolare l'ennesimo numero della serie di Fibonacci. Un elemento si ottiene sommando i precedenti due elementi avendo posto uguale a 1 i primi due:
function fibonacci(n)
     if n < 2 then
          return 1
     end
     
     local n1, n2 = 1, 1
     for i = 1, n-1 do
          n1, n2 = n2, n1 + n2 -- assegnazione multipla
     end
     return n1
end

print(fibonacci(10)) --> 55

Con le regole dell'assegnazione multipla una funzione può accettare più argomenti. Se essa verrà chiamata con più argomenti rispetto a quelli che essa prevede quelli in eccesso verranno ignorati mentre, viceversa, se gli argomenti sono inferiori a quelli previsti allora a quelli mancanti verrà assegnato il valore nil.
Ma questo vale anche per i dati di ritorno quando la funzione è usata come espressione in un'istruzione di assegnamento. Basta inserire dopo l'istruzione return la lista delle espressioni separate da virgola che saranno valutate e assegnate alle corrispondenti variabili.

Per esempio, potremo modificare la funzione precedente per restituire la somma dei primi n numeri di Fibonacci oltre che solamente l'ennesimo elemento della serie stessa e considerare un valore di default se l'argomento è nil:

function fibonacci(n)
     n = n or 10 -- the default value is 10
     if n == 1 then
          return 1, 1
     end
     
     if n == 2 then
          return 1, 2
     end
     
     local sum = 1
     local n1, n2 = 1, 1
     for i = 1, n-1 do
          n1, n2 = n2, n1 + n2
          sum = sum + n1
     end
     return n1, sum
end

local fib_10, sum_fib_10 = fibonacci()
print(fib_10, sum_fib_10)

Funzioni: valori di prima classe, I

In Lua le funzioni sono un tipo. Possono essere assegnate a una variabile e passate come argomento a una funzione.

Questa proprietà non si trova spesso nei linguaggi di scripting e offre una nuova flessibilità al codice.

Tutte le funzioni sono memorizzate in variabili. Per assegnare direttamente una funzione a una variabile esiste in Lua la sintassi anonima:
local add = function (a, b)
    return a + b
end

print(add(45.4564, 161.486))

Essendo le funzioni valori di prima classe ne consegue che in Lua le funzioni sono oggetti senza nome esattamente come lo sono tipi come i numeri e le stringhe. Inoltre, la sintassi classica di definizione
function variable_name (args)
    -- function body
end
è solo zucchero sintattico perché l'interprete Lua la tradurrà effettivamente e automaticamente nel codice equivalente
variable_name = function (args)
    -- function body
end

Funzioni: valori di prima classe, II


Un esempio di funzione con un argomento funzione è il seguente, una funzione esegue un numero di volte dato la stessa funzione priva di argomenti:
function print_five()
    print(5)
end

function do_many(fn, n)
    for i=1, n or 1 do
        fn()
    end
end

do_many(print_five)
do_many(print_five, 10)

do_many(function () print("---") end, 12)

Molto interessante, anzi senza dubbio fantastico. Nell'ultima riga di codice l'argomento è una funzione definita in sintassi anonima che verrà eseguita 12 volte.

Per prendere confidenza con il concetto di funzioni come valori di prima classe, cambiamo il significato della funzione precostruita in Lua 'print()'. Ecco come:
local println = print
print = function (n)
    println("Argomento funzione -> "..n)
end

print(12)

Tabelle e funzioni

Se una tabella può contenere chiavi con qualsiasi valore allora può contenere anche funzioni!
Le sintassi sono queste --- esplicitate con il codice riportato di seguito:
  1. assegnare la variabile di funzione a una chiave di tabella;
  2. assegnare direttamente la chiave di tabella con la definizione di funzione in sintassi anonima;
  3. usare il costruttore di tabelle per assegnare funzioni in sintassi anonima.

-- primo caso
local function tipo_i()
    -- body
end

local t = {}
t.func_1 = tipo_i

-- secondo caso
local t = {}
t.func_2 = function ()
    -- body
end

-- terzo caso con più di una funzione
local t = {
    func_3_i = function ()
        -- body
    end,
    
    func_3_ii = function ()
        -- body
    end,
    
    func_3_iii = function ()
        -- body
    end,
}

Con questo meccanismo una tabella può svolgere il ruolo di modulo memorizzando funzioni utili a un certo scopo e in effetti la libreria standard di Lua si presenta all'utente proprio in questo modo.

Variadic arguments

Una funzione può ricevere un numero variabile di argomenti rappresentati da tre dot consecutivi '...'. Nel corpo della funzione i tre punti rappresenteranno la lista degli argomenti, dunque possiamo o costruire con essi una tabella oppure effettuare un'assegnazione multipla.

Un esempio è una funzione che restituisce la somma di tutti gli argomenti numerici:
-- per un massimo di 3 argomenti
local function add_three(...)
    local n1, n2, n3 = ...
    return (n1 or 0) + (n2 or 0) + (n3 or 0)
end

-- con tutti gli argomenti
local function add_all(...)
    local t = {...} -- collecting args in a table
    local sum = 0
    for i = 1, #t do
        sum = sum + t[i]
    end
    return sum
end

print(add_three(40, 20))
print(add_all(45, 48, 5456))
print(add_three(14, 15), add_all(-89, 45.6))

Per inciso, anche la funzione base 'print()' accetta un numero variabile di argomenti.
Il meccanismo è ancora più flessibile perché tra i primi argomenti vi possono essere variabili "fisse".
Per esempio il primo parametro potrebbe essere un moltiplicatore:
local function add_and_multiply(molt, ...)
    local t = {...}
    local sum = 0
    for i = 1, #t do
        sum = sum + t[i]
    end
    
    return molt * sum
end

print(add_and_multiply(10, 45.23, 48, 9.36, -8, -56.3))

Un'altra funzione predefinita 'select()' consente di accedere alla lista degli argomenti in dettaglio. Infatti se tra gli argomenti compare un valore nil avremo problemi ad accedere ai paraemetri successivi nel codice precedente perché --- come sappiamo già --- l'operatore di lunghezza # considera il nil come valore sentinella di fine array/tabella.

Il selettore prevede un primo parametro fisso e la lista variabile inserita con i tre punti '...'. Se questo parametro è un intero allora questo verrà utilizzato come indice per restituire il corrispondente parametro. Se invece il parametro è la stringa "#" restitisce il numero di argomenti extra presenti dopo l'eleventuale nil intermedio.
Il codice seguente preso pari pari dal PIL --- certamente il punto di riferimento principale su Lua scritto dallo stesso Autore del linguaggio Roberto Ierusalimschy, tra l'altro composto in LaTeX e venduto come contributo al progetto Lua stesso:
for i = 1, select("#", ...) do
    local arg = select(i, ...)
    -- loop body
end

Omettere le parentesi tonde se...

In Lua esiste la sintassi di chiamata a funzione semplificata, ammessa opzionalmente solo se:
  • la funzione accetta un unico argomento di tipo stringa;
  • la funzione accetta un unico argomento di tipo tabella.
e consiste nella possibilità di ommettere le parentesi tonde ().
Per esempio:

print "si è possibile anche questo..."

local function is_empty(t)
    if #t == 0 then
        return true
    else
        return false
    end
end

-- questo:
print(is_empty{})
print(is_empty{1, 2, 3})

-- invece di questo (sempre possibile):
print(is_empty({}))
print(is_empty({1, 2, 3}))

Closure

Chiudiamo la puntata con uno strano termine forse meglio noto agli sviluppatori dei linguaggi funzionali: la closure.

Questa proprietà di Lua amplia il concetto di funzione rendendo possibile l'accesso dall'interno di essa ai dati presenti nel contesto esterno. Ciò è possibile perché alla chiamata di una funzione viene creato uno spazio di memoria del contesto esterno unico e indipendente.

Tutte le chiamate a una stessa funzione condivideranno una stessa closure.

Se questo è vero una funzione potrebbe incrementare un contatore creato al suo interno, e anche qui prendo l'esempio di codice dal PIL:
local function new_counter()
    local i = 0 -- variabile nel contesto esterno
    return function ()
        i = i + 1 -- accesso alla closure
        return i
    end
end

local c1 = new_counter()
print(c1()) --> 1
print(c1()) --> 2
print(c1()) --> 3
print(c1()) --> 4
print(c1()) --> 5

local c2 = new_counter()
print(c2()) --> 1
print(c2()) --> 2
print(c2()) --> 3

print(c1()) --> 6

A parole il codice definisce una funzione 'new_counter()' che restituisce una funzione che ha accesso indipendente al contesto (la variabile 'i').
Come si verifica dalle righe successive a ogni chiamata della funzione contatore il valore catturato è unico e indipendente.

Tecnicamente la closure è la funzione effettiva mentre invece la funzione non è altro che il prototipo della closure.

Le closure consentono di implementare diverse tecniche utili in modo naturale e concettualmente semplice. Una funzione di ordinamento potrebbe per esempio accettare come parametro una funzione di confronto per stabilire l'ordine tra due elementi tramite l'accesso a una seconda tabella esterna contenente informazioni utili per l'ordinamento stesso.

Nel prossimo esempio mettiamo in pratica l'idea appena presentata. Il codice utilizza una funzione della libreria di Lua, che introdurremo nella prossima puntata, in particolare table.sort(), per applicare l'algoritmo di ordinamento alla tabella passata come argomento in base al criterio di ordine stabilito con la funzione passata come secondo argomento in sintassi anonima.

local years = {1994, 1996, 1998, 2000, 2002}
local val = {
    [1994] = 12.5,
    [1996] = 10.2,
    [1998] = 10.9,
    [2000] =  8.9,
    [2002] = 12.9,
}

local function sort_by_value(tab)
    table.sort(tab,
        function (a, b)
            return val[a] > val[b]
        end
    )
end

sort_by_value(years)

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

Esercizi


1 - Scrivere una funzione che sulla base della stringa in ingresso "+", "-", "*", "/" restituisca la funzione corrispondente per due argomenti.

2 - Scrivere la funzione che accetti due argomenti numerici e ne restituisca i risultati delle quattro operazioni aritmetiche.

3 - Scrivere una funzione che restituisca il fattoriale di un numero memorizzandone in una tabella di closure i risultati per evitare di ripetere il calcolo in chiamate successive con pari argomento.

4 - Scrivere una funzione con un argomento opzionale rispetto al primo parametro numerico che ne restituisca il seno interpretandolo in radianti se l'argomento opzionale è nil oppure "rad", in gradi sessadecimali se "deg" o in gradi centesimali se "grd".

5 - Scrivere una funzione che accetti come primo argomento una funzione f: R -> R (prende un numero e restituisce un numero), come secondo e terzo argomento i due valori dell'intervallo di calcolo e come quarto argomento il numero di punti in cui suddividere l'intervallo. La funzione dovrà stampare i valori che la funzione argomento assume nei punti definiti.

Riassunto della puntata

Una puntata bella densa dedicata alle funzioni di Lua caratterizzate dall'essere valori di prima classe --- o anche valori higher-order --- e dalle closures. Niente male per un linguaggio di scripting ;-)

martedì 7 aprile 2015

Che tipo di triangolo è? [Python3]

Ripropongo lo stesso esercizio fatto con bash qualche giorno fa (qui). Questa volta con Python, però. L'esercizio consiste nello scrivere uno script che, dopo avergli passato tre interi, mi restituisca un tipo di triangolo: equilatero, isoscele o scaleno.

Perchè questi esercizi? Perchè solo scrivendo e leggendo codice si impara a programmare. I manuali stanno alla base, ma non bastano, serve pratica. E molta.

Ok, passiamo allo script di oggi. Se ritenete che qualcosa non vada, scrivetelo pure nei commenti.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

def tipo_triangolo():
    a=input("inserisci la misura del primo lato: ")
    if a.isdecimal():
        a=int(a)
    else:
        print(a, "non è un intero valido")
        raise SystemExit
    b=input("inserisci la misura del secondo lato: ")
    if b.isdecimal():
        b=int(b)
    else:
        print(b, "non è un intero valido")
        raise SystemExit
    c=input("inserisci la misura del terzo lato: ")
    if c.isdecimal():
        c=int(c)
    else:
        print(c, "non è un intero valido")
        raise SystemExit

    if a+b>c and a+c>b and b+c>a:
        if a==b and b==c:
            print("triangolo equilatero")
        elif a==b or a==c or b==c:
            print("triangolo isoscele")
        else:
            print("triangolo scaleno")
    else:
        print("Non è stata rispettata la disuguaglianza triangolare")

tipo_triangolo()

La funzione input() mi restituisce sempre una stringa, anche quando digito un numero.

Provare per credere.


Bisogna fare in modo di trasformare quella stringa in un intero, qualora volessimo utilizzarlo come intero, ovvio.

Deputato a fare una cosa del genere è int(), ma non basta, serve un passaggio intermedio; infatti bisogna prima controllare se quella stringa possa o meno essere convertita in un intero, a tal fine utilizzeremo la funzione isdecimal().

isdecimal() darà come risultato semplicemente o true o false.

Se il controllo di isdecimal() sarà superato, nel senso che risponderà "true", allora la stringa sarà convertibile in intero, perciò la passo a int(), che la convertirà; diversamente lo script mi dirà che ciò che abbiamo dato in pasto ad input() è un intero non valido e restituirà un exit 1.

La prima parte dello script è volutamente prolissa, avrei potuto abbreviarlo di molto. La sintesi, tuttavia, non è sempre un bene.

Nella seconda parte si decide il tipo del triangolo, ma solo se è stata rispettata la disuguaglianza triangolare, cioè che la somma della misura di due lati sia maggiore della misura del terzo.


Ovviamente potete offrire le vostre soluzioni. A presto :)

domenica 5 aprile 2015

Che tipo di triangolo è? [Brevi Esercizi]

Salve!

È la vigilia di Pasqua, cosa faccio cosa non faccio, alla fine decido di scrivere uno script in bash.

Lo script chiede tre numeri interi, che sarebbero le misure dei lati di un triangolo, e restituisce il tipo del triangolo stesso, equilatero, isoscele, scaleno.

Può sembrare un'operazione semplice, ma in realtà non lo è. E non lo è per tutta una serie di ragioni. Anzitutto perché bash, confrontato con altri linguaggi, come Python ad esempio, non si presta molto a queste cose. Ed è proprio questo il bello. Spingersi oltre per vedere l'effetto che fa.

E poi, quando tutto sembra funzionare, vieni assalito da mille dubbi. E se al posto di un numero intero passi una stringa o un numero pari a zero o addirittura negativo? E poi bastano veramente tre lati per costruire un triangolo? Sì, bastano tre lati, ma non tre lati qualsiasi. Avete mai sentito parlare di disuguaglianza triangolare? La regola della disuguaglianza triangolare dice che ogni lato deve essere inferiore alla somma degli altri due. Vi faccio un esempio. Vi do la misura di tre lati, 1m, 2m, 80m, provate ora a costruire mentalmente un triangolo...Ecco, evidentemente un triangolo consta di tre lati, ma non basta, serve dell'altro.

E se passo allo script un numero decimale? Un casino, dovrei riscrivere alcune parti...magari lo lascio come esercizio, ci sarebbe bc... :)

Con Python è tutto più semplice, con bash mica tanto. Io ci ho provato. Sicuramente l'esercizio è migliorabile, perfezionabile e via dicendo.

#!/bin/bash

read -p "Digita la misura del primo lato: " a
read -p "Digita la misura del secondo lato: " b
read -p "Digita la misura del terzo lato: " c 


for i in $a $b $c; do
      if [ $i -gt 0 ] 2>/dev/null; then
             echo "" 
      else
             echo "devi immettere numeri interi e maggiori di zero" && exit 1
      fi
done


let d=$b+$c
let e=$a+$c
let f=$a+$b

if [[ $a -gt $d || $b -gt $e || $c -gt $f ]]; then
 clear
 echo -e "\nNon è stata rispettata la disuguaglianza triangolare\n" && exit 1
fi       


ordina=$( printf '%s\n' $a $b $c | sort -n )
risultato=$( uniq -c <<< "$ordina" | wc -l )

clear
case $risultato in
    1) echo -e "\nTRIANGOLO EQUILATERO\n" ;;
    2) echo -e "\nTRIANGOLO ISOSCELE\n"   ;;
    3) echo -e "\nTRIANGOLO SCALENO\n"    ;;
esac

Con read -p chiedo i tre lati. Poi il primo controllo. Se digito una stringa al posto di un numero intero oppure, se digito un numero non maggiore di zero, lo script mi ritorna exit 1. Ho usato un ciclo for, e il controllo lo fa l'operatore -gt che accetta solo numeri interi.
Poi il secondo controllo, quello della disuguaglianza triangolare, se ogni lato non è inferiore alla somma degli altri due, lo script mi ritorna un exit 1.

Passato questo secondo controllo, magari ne potremmo aggiungere altri, ma ora non mi viene niente, si passa all'elaborazione dei dati.

Printf mette in colonna i dati e sort -n li ordina. I dati ordinati vengono dati in pasto a uniq -c attraverso il "<<<", che si chiama here strings, e poi passati in input a wc -l.
P

Per capire che fa uniq -c dobbiamo fare un esempio. Poniamo che passi allo script i seguenti dati: 2 1 2

Questi tre dati vengono messi in colonna da printf:

2
1
2

Poi sort -n li ordina, dal più piccolo al più grande:

1
2
2

uniq -c conta quante volte compare un dato, in questo caso:

1 1
2 2

Infine wc -l conta quante righe ci sono. Se le righe sono 3, il triangolo è scaleno, se ce ne sono due, il triangolo è isoscele, se una, è equilatero.

Facciamo un altro esempio. Passo allo script i seguenti dati:  6, 4, 5;

printf li mette in colonna, sort -n li ordina, uniq -c conta le frequenze di ogni numero. In questo caso:

1 4
1 5
1 6

wc -l darà come risultato 3, che significa tre lati disuguali, quindi triangolo scaleno. E sarà il case a darci una risposta.


Alla prossima! E Buona Pasqua.

sabato 4 aprile 2015

Corso Lua - puntata 5


Operatori logici

Gli operatori logici 'and' 'or' e 'not' danno luogo ad alcune espressioni idiomatiche di Lua.
Cominciamo con 'or': è un operatore logico binario. Se il primo operando è vero lo restituisce altrimenti restituisce il secondo.

Ricordatevi che in Lua un'espressione è vera se è il valore 'true' oppure se è un valore non nil, quindi per esempio nel seguente codice 'a' vale sempre 123.

local a = 123 or "mai assegnato"
print(a)


L'operatore 'and' restituisce il primo operando se esso è falso altrimenti restituisce il secondo operando.

Con and e or combinati otteniamo l'operatore ternario del C++ che è "a ? b:c" in Lua: Ecco l'espressione in un esempio di codice: se 'a' è vero restituisce 'b' altrimenti 'c':

local val = (a and b) or c

Poiché 'and' ha priorità maggiore rispetto a 'or' nell'espressione precedente possiamo omettere le parentesi per un codice più idiomatico:

local val = a and b or c


Il massimo tra due numeri è un'espressione condizionale:

local x = 45.69
local y = 564.3

local max
if x > y then
    max = x
else
    max = y
end

ma con gli operatori logici è tutto più Lua...
local x = 45.69
local y = 564.3

local max = (x > y) and x or y

L'operatore logico 'not' restituisce true se l'operando è nil oppure se è false --- valore booleano --- e viceversa restituisce false se l'operando non è nil oppure è true. Alcuni esempi:
print(not 5)       --> 'false'
print(not not 5)   --> 'true'
print(not true)    --> 'false'
print(not false)   --> 'true'
print(not nil)     --> 'true'

L'operatore di negazione può essere usato per controllare se una variabile è valida oppure no. Per esempio possiamo controllare se in una tabella esiste un campo 'prezzo':
local t = {} -- una tabella vuota

if not t.prezzo then -- t.prezzo è nil
    print("assente")
else
    print("presente")
end

t.prezzo = 12.00
if not t.prezzo then
    print("assente")
else
    print("presente")
end

Le stringhe

In Lua le stringhe rappresentano uno dei tipi di base del linguaggio.
Per rappresentare valori stringa letterali ci sono tre diversi delimitatori:
  • doppi apici: carattere ";
  • apice semplice: carattere ';
  • doppie parentesi quadre: delimitatori [[ e ]] con o senza =.

In una stringa delimitata da doppi apici possiamo inserire liberamente apici semplici e viceversa, e caratteri non stampabili come il ritorno a capo (\n) e la tabulazione (\t), tramite il carattere di escape backslash che quindi va inserito come doppio backslash:
local s1 = "doppi 'apici'"
local s2 = 'apici semplici e non "doppi"'
local s3 = "prima riga\nseconda riga"
local s4 = "una \\macro"
local s5 = "\""
assert( s5 == '"')

print(s1)
print(s2)
print(s3)
print(s4)
print(s5)

In Lua non esiste il tipo carattere quindi gli Autori del linguaggio hanno pensato di utilizzare i delimitatori normalmente destinati a rappresentare i char per consentire all'utente di creare stringhe contenenti i delimitatori doppi apici e viceversa, senza utilizzare l'escaping. Infatti i caratteri \" e \' rappresentano comunque i caratteri apici corrispondenti, come si vede nella variabile s5 del codice precedente.

Il terzo tipo di delimitatore per le stringhe è una coppia si parentesi quadre e ha la proprietà di ammettere il ritorno a capo. Si possono così introdurre nel sorgente interi brani di testo nel quale i caratteri di escaping non saranno interpretati.
local long_text = [[
Questo è un testo multiriga
dove i caratteri di escape non contano
come \n o \" o \' o \\.

Inoltre, se il testo come in questo caso
comincia con un ritorno a capo allora questo
carattere \n sarà ignorato.
]]

print(long_text)

Se per caso nel testo fossero presenti i delimitatori di chiusura è possibile inserire un numero qualsiasi di caratteri '=' tra le parentesi quadre, purché il numero sia lo stesso per i delimitatori di apertura e chiusura, esempio:
local long_text = [=[
Questo è il codice Lua da stampare:

local tab = {10, 20, 30}
local idx = {3, 2, 1}
print(tab[idx[1]]) -- notare doppia parentesi quadra chiusa

local long_text = [[ -- questo non potremo farlo...
    Testo lungo...
]]

Tutto chiaro?
In Lua le stringhe letterali nel codice
possono essere proprio letterali
senza caratteri di escape e senza
preoccupazioni sulla presenza di gruppi
di delimitazione di chiusura...
]=]

print(long_text)

Commenti multiriga

Questi delimitatori variabili con numero qualsiasi di segni '=' li troviamo nei commenti multiriga di Lua. Abbiamo incontrato fino a ora i commenti di riga che si introducono nel codice con un doppio trattino '--'.

I commenti multiriga sono comodi quando si vuol escludere dall'esecuzione un intero blocco di righe: iniziano con i doppi trattini seguiti da un delimitatore di stringa multiriga e terminano con la corrispondente chiusura:
-- questo è un commento di riga

--[[
questo è un commento
multiriga
]]

--[=[
e anche questo è un commento
multiriga
]=]

Normalmente in Lua i commenti multiriga vengono chiusi premettendo i doppi trattini anche al gruppo delimitatore di chiusura. Questo è solo un trucco per riattivare rapidamente il codice eventualmente contenuto nel commento, basta uno spazio per far diventare il commento multiriga :
--[[ righe di codice non attive

local tab = {}
--]]

-- [[ notare lo spazio dopo i doppi trattini
-- questo codice invece viene eseguito

local tab = {}
--]] -- e questo diventa una normale riga di commento

L'editor SciTE che vi consiglio per scrivere il codice Lua, è in grado di colorare correttamente il testo dei commenti di riga e di quelli multiriga.

Concatenare stringhe e immutabilità

In Lua l'operatore '..' concatena due stringhe, in questo modo:
local s1 = "Hello" .. " " .. "world"
local s2 = s1 .. " Ok"
s2 = s2 .. "."

print(s1 .. "!")
print(s2)

Il concetto importante riguardo alle stringhe è se queste siano o no immutabili. Se non lo sono la concatenazione di stringhe non comporta la creazione di una nuova stringa risultato dell'operazione. In Lua, come in molti altri linguaggi, le stringhe sono invece immutabili.
Ciò significa che una volta create le stringhe non possono essere modificate e nel codice precedente, l'operazione di concatenare il carattere punto in coda alla stringa 's2', genera una nuova stringa che è assegnata alla stessa variabile.

Per poche operazioni di concatenazione ciò non è un problema, ma in alcuni casi invece si. Consideriamo il seguente codice apparentemente innocuo:
local s = ""

for i = 1, 100 do
    s = s .. "**"
end

print(#s) -- l'operatore length funziona anche per le stringhe!

Ma cosa succede in dettaglio? Perché questo codice non è efficiente?
Ad ogni concatenazione viene creata una nuova stringa. La prima volta vengono copiati due byte per dare la stringa "**". La seconda iterazione la memoria copiata sarà di 4 byte, e alla terza di 6 byte, eccetera.
Ad ogni iterazione la memoria copiata cresce di due byte con il risultato che per produrre una stringa di 200 asterischi (200 byte) avremo copiato in totale la memoria equivalente a 10100 byte!

In Java e negli altri linguaggi con stringhe immutabili normalmente si corre ai ripari mettendo a disposizione una struttura dati o una funzione che risolve il problema, per esempio un tipo StringBuffer. In Lua la soluzione è una funzione della libreria table che, anticipando rispetto alle nostre chiaccherate è table.concat():
local t = {}
for i = 1, 100 do
    t[#t + 1] = "**"
end

print(#table.concat(t))

Per la cronaca, nel caso specifico avremo dovuto usare la funzione string.rep() ma table.concat() è più generale.

Esercizi


1 - Prevedere il risultato delle seguenti espressioni Lua:
local a = 1 or 2
local b = 1 and 2
local c = "text" or 45

local d = not 12 or "ok"
local e = not nil or "ok"

2 - Usando gli operatori logici di Lua codificare l'espressione che restituisce la stringa "grande", "uguale" o "piccolo" a seconda dei valori numerici contenuti in due variabili.

3 - Come fare in Lua per creare una stringa letterale contenente sia il carattere apice semplice che doppio?

4 - Quale sarà il risultato dell'esecuzione del seguente codice?

local s = "'"..'"'.."ok"..[["']]
print(s)

5 - Creare la stringa "\/".

6 - Scrivere un programma che a partire dalla stringa "*" crei e stampi la stringa di 64 asterischi senza utilizzare l'operatore di concatenazione o la funzione string.rep().

7 - Scrivere un programma che a partire dalla stringa "*" crei e stampi la stringa di 64 asterischi usando l'operatore di concatenazione il minimo indispensabile di volte.

Riassunto della puntata

Oggi abbiamo introdotto gli operatori logici, le modalità di inserimento delle stringhe e la loro concatenazione. Una puntata un po' noiosa se vogliamo, ma questi dettagli devono pur essere conosciuti per programmare.

D'altra parte non possiamo --- specie per le stringhe --- studiare esempi significativi fino a che non avremmo introdotto la libreria standard di Lua che a sua volta non possiamo introdurre fino a che non avremmo parlato delle funzioni.

E allora facciamolo, la prossima volta parleremo delle funzioni in Lua.
Alla prossima.