Upload
stesasso
View
2.816
Download
2
Embed Size (px)
DESCRIPTION
Guida alla programmazione in ruby partendo da zero: spiegazioni, esempi, esercizi.
Citation preview
Imparando Ruby...
Introduzione alla programmazione con Ruby
a cura di
Stefano Sasso
stefano(at)gnustile.net
Versione 1.0 - 10 Febbraio 2009
Indice
I Le Basi 1
1 Introduzione a Ruby 2
1.1 Interprete interattivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Interprete non interattivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 La prima prova . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Il linguaggio Ruby 4
2.1 Metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Assegnazione di variabili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Tipi di dato numerici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 I commenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.5 Le costanti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.6 Tutti i metodi di un oggetto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.7 Standard input + Stringhe di caratteri . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.8 Conversione tra diversi tipi di dato . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.9 Dati booleani . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.10 Enunciati decisionali - IF/UNLESS . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.11 Altro tipo di dato: gli array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.12 Cicli di istruzioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.1 while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.2 for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12.3 blocco each - blocco times . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.13 Ancora array... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.14 Ancora stringhe... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.15 Definizione di metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.16 Semplici operazioni con file di testo . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.16.1 lettura di un file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
i
2.16.2 scrittura di un file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.16.3 lavorare con il filesystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.17 Altri oggetti utili . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.17.1 La classe ’Time’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.17.2 La classe ’Hash’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3 Programmazione orientata agli oggetti 23
3.1 Definizione di classe e oggetto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Creazione di classi personalizzate . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.1 costruzione di una classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.2.2 variabili di istanza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.2.3 ereditarieta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.3 Modifica di classi esistenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.4 Gestione degli errori - Eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4 Uso di librerie particolari 32
4.1 Gemme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 Lavorare con indirizzi IP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.3 YAML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.4 Inviare mail via SMTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.5 Accesso a database MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.6 Accesso a database SQLite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.7 Accesso a database KirbyBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.8 ActiveRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.9 File XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.9.1 xmlsimple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.9.2 rexml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.10 Networking a basso livello . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
II Programmazione web di base 46
5 CGI 47
5.1 Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2 CGI in ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.3 CGI + ERB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.4 mod ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
ii
III Ruby on Rails 51
A Esercizi e soluzioni 53
A.1 Semplici operazioni con stringhe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.1.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2 Metodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
A.2.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
A.3 Operazioni con Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
A.3.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
A.3.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.3.3 Esercizio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
A.4 Operazioni con Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.4.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
A.5 Operazioni con File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
A.5.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
A.6 Operazioni con Oggetti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.1 Esercizio 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.2 Esercizio 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
A.6.3 Esercizio 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
A.6.4 Esercizio 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
A.6.5 Esercizio 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
iii
Parte I
Le Basi
1
Capitolo 1
Introduzione a Ruby
Ruby e un linguaggio di programmazione interpretato, questo significa che passando un file ditesto contentente codice ruby all’interprete, questo viene eseguito.E possibile scrivere spezzoni di codice ruby anche in un interprete interattivo, il che significa cheappena uno spezzone di codice viene inserito, lo stesso viene eseguito.
1.1 Interprete interattivo
E possibile lanciare l’interprete interattivo con
~# irb
ci comparia quindi un prompt simile al seguente:
irb(main):001:0> _
1.2 Interprete non interattivo
Per lanciare l’interprete non interattivo e necessario scrivere all’interno di un file di testo il codiceruby che vogliamo venga eseguito.Possiamo poi lanciare l’interprete con
~# ruby nomedelfile.rb
oppure, piu semplicemente, inserire in testa al file contenente il codice ruby la riga
#!/usr/bin/env ruby
come qualsiasi altro script in ambiente UNIX.
2
1.3 La prima prova
Facciamo subito la prima prova: creiamo il file prova1.rb con un editor di testo, e al suo internoscriviamo
puts "Ciao mondo!"
poi eseguiamolo con
~# ruby prova1.rb
allo stesso modo apriamo irb, e al prompt digitiamo
puts "Ciao mondo!"
il nostro interprete sara quindi in questa situazione:
irb(main):001:0> puts "Ciao mondo!"
Ciao mondo!
=> nil
irb(main):002:0> _
Analizziamo ora questo semplicissimo programma:puts e una funzione del linguaggio che ci consente di mandare qualche oggetto allo standardoutput.Ruby e un linguaggio orientato agli oggetti ; per il momento consideriamo gli oggetti come delle“cose” che vengono manipolate da un programma.
In questo caso l’oggetto in questione, da stampare a schermo, e una stringa di caratteri, delimitatada doppi apici (′′). Equivalente sarebbe stato delimitarla da apici singoli (’).
3
Capitolo 2
Il linguaggio Ruby
2.1 Metodi
Abbiamo visto prima che in Ruby esistono delle funzioni, comunemente detti metodi che compionouna qualche azione. Ad esempio puts serve per stampare a schermo un oggetto.Esistono piu modi per richiamare un metodo da un programma: con o senza parentesi tonde ( ).
metodo parametro1, parametro2
metodo(parametri1, parametro2)
parametro1 e parametro1 sono degli oggetti su cui il metodo specifico andra ad operare. Il numerodi oggetti “passabili” a un metodo e deciso dal progettista del metodo stesso.
Nel contesto di prima, ad esempio,
puts "Ciao mondo!"
e
puts("Ciao mondo!")
sono del tutto equivalenti. Il metodo puts puo lavorare, come gia visto, su degli oggetti stringadi caratteri, ma anche, ad esempio, su oggetti del tipo numero intero (ma anche altri):
puts 12
puts(12)
Ricordiamoci che in Ruby, tutto e un oggetto, anche i numeri.
Su ogni oggetto e possibile invocare dei metodi, che lavoreranno direttamente su quell’oggetto. Epossibile anche invocare dei metodi su dei metodi ; questo perche un metodo ritorna al programmasempre un oggetto, quindi in realta e come invocare un metodo su un oggetto. vediamo un esempio(lavorando da irb):
3.next
4
in questo caso invochiamo il metodo next sull’oggetto 3. Il metodo ritornera come valore di uscita4. Ora, invocando il metodo next sull’oggetto 4, otterremo 5.Lo stesso risultato si sarebbe potuto ottenere con
3.next.next
E da ricordare che ogni tipo di oggetto (chiamato classe) ha i suoi metodi, anche se piu oggettipossono avere dei metodi che si chiamano nello stesso modo.
Ad esempio
"a".next
non e lo stesso metodo visto prima (infatti, la classe di partenza e diversa), ma si chiama nellostesso modo.Ad esempio,
"ciao".length
ha senso (lunghezza della stringa), mentre
2.length
no.
Ah, nel frattempo abbiamo visto che l’operatore per invocare dei metodi su degli oggetti e il .(punto).
Nel caso di numeri, le operazioni matematiche sono anch’esse metodi.Ad esempio
3+4
viene automaticamente convertito da ruby in
3.+(4)
ovvero viene invocato il metodo che si chiama + sull’oggetto 3.Idem per -, *, /, **
I metodi che agiscono su un oggetto di norma ritornano un altro oggetto; per ritornare non siintende ritornare qualcosa all’utente, bensı ritornare qualcosa (un oggetto) al programma.
(Capiremo meglio il significato di ritornare andando avanti con le nozioni imparate.)
Ad esempio, i metodi
3.next
3.+(2)
ritornano, rispettivamente, oggetti numero 4 e 5.
A volte i metodi possono ritornare un oggetto particolare, l’oggetto nil, che sta per nulla. Adesempio, nel caso di puts, non ha senso che venga ritornato un qualcosa, quindi ritornera nil.
5
2.2 Assegnazione di variabili
Un operatore particolare e l’operatore di assegnazione, =, che consente di salvare il valore (oggetto)ritornato da un metodo all’interno di un’area della memoria, chiamata variabile.
Ad esempio
risultato=2+5
L’area della memoria da noi chiamata risultato conterra ora l’oggetto 7, ritornato dal metodo+ con parametro 5 invocato sull’oggetto 2.Su un’area di memoria (variabile) e possibile invocare tutti i metodi propri dell’oggetto che vi ememorizzato:
risultato2=risultato.next
la variabile risultato2 conterra ora l’oggetto numerico 8.
2.3 Tipi di dato numerici
Visto che ne stavamo parlando, vediamo ora come sono esprimibili i tipi di dato numerici.Fondamentalmente e possibile esprimere un numero in due notazioni:
• Numero intero (Classe: Fixnum)
• Numero a virgola mobile (Classe: Float)
vediamo qualche esempio per comprendere meglio la “divisione”:
3 (classe: Fixnum)
-5 (classe: Fixnum)
4.33564 (classe: Float)
-4.5 (classe: Float)
1.34E98 (classe: Float)
-1.43E12 (classe: Float)
1.2E-5 (classe: Float)
-3.6E-3 (classe: Float)
ad esempio, gli oggetti della classe Float non hanno il metodo next, se hai studiato matematicacapirai il perche :)
2.4 I commenti
I commenti sono parti di testo all’interno del codice, non vengono eseguiti dall’interprete mapossono essere utili per annotare cose che servono al programmatore.
6
# commento su una riga
=begin
commento
su
piu‘
righe
=end
puo essere utile ad esempio, in questo caso
# la variabile a contiene l’anno corrente
a=MioOggetto.mio_metodo
2.5 Le costanti
Le costanti sono come delle variabili e si usano per contenere dei valori che devono venir ripetutipiu volte all’interno del programma.Si consiglia di usare nomi simbolici e descrittivi per le costanti, per facilitare la leggibilita delprogramma.Una costante si rappresenta con il nome formato da sole lettere maiuscole.esempio, conversione Euro:
FATTORE_DI_CONVERSIONE=1936.27
euro=lire/FATTORE_DI_CONVERSIONE
2.6 Tutti i metodi di un oggetto
Come detto prima, ogni oggetto ha i suoi metodi. Per sapere quali sono i metodi disponibili in unoggetto basta cercare su google
ruby+Classe
ad esempio
ruby+String
il primo risultato sara qualcosa del tipo
http://www.ruby-doc.org/core/classes/String.html
Proviamo a darci una rapida occhiata.
Se abbiamo un oggetto, ma non ne conosciamo il tipo, possiamo invocare il metodo class
sull’oggetto stesso per conoscerne la classe di “partenza”.
5.class
"ciao".class
3E2.class
7
2.7 Standard input + Stringhe di caratteri
Consideramo questo breve programmino:
puts "Come ti chiami?"
nome = gets
nome.chomp!
puts "Ciao, " + nome
puts l’abbiamo gia visto, gets invece e una funzione che recupera una riga dallo standard inpute la ritorna in una variabile di tipo stringa.Perche poi invochiamo il metodo chomp! su quell’oggetto?
Perche gets ritorna l’input inserito COMPRENSIVO di ritorno a capo (carattere speciale \n ),quindi chomp! serve a rimuovere proprio questo carattere dalla fine della stringa.
Volendo, si sarebbe anche potuto scrivere
nome = gets.chomp
la differenza tra chomp e chomp! ? vedere tra i metodi nella documentazione ufficiale :)comunque, per convenzione, i metodi! agiscono direttamente sull’oggetto, mentre i metodi neritornano una copia (modificata). vediamo un esempio:
a="ciao\n"
b=a.chomp
# a vale "ciao\n"
# b vale "ciao"
a.chomp!
# a vale "ciao"
Nel programmino di prima vediamo anche come sia possibile concatenare 2 stringhe, ovvero il“Ciao, “ e nome. (sempre l’operatore-metodo +)Sarebbe anche stato possibile includere la stringa nome direttamente all’interno della stringadefinita con “ “:
puts "Ciao, #{nome}"
La serie di caratteri # permette di includere in una stringa il contenuto di una variabile.esempio:
var1="bao"
var2="ciao #{var1}"
var2 conterra quindi “ciao bao“.
8
2.8 Conversione tra diversi tipi di dato
Su alcuni oggetti -tipi di dato e possibile invocare dei metodi per la conversione in altri oggetti -tipidi dato.Questi metodi si chiamano to , ad esempio
• .to i - converte in numero intero
• .to f - converte in numero con virgola
• .to s - converte in stringa
esempio:
k="ccc".to_i #=> k vale 0
k="ccc".to_f #=> k vale 0.0
k="ccc".to_s #=> k vale "ccc"
k="c123d4".to_i #=> k vale 0 (la stringa non e‘ convertibile)
k="123".to_i #=> k vale 123
k=123.to_s #=> k vale "123"
k=123.to_f #=> k vale 123.0
k=1.23.to_i #=> k vale 1
2.9 Dati booleani
Oltre a quelli gia visti, ovvero numeri e stringhe di caratteri, esiste un altro tipo di dato “fonda-mentale”: il tipo booleano. I dati booleani rispecchiano l’algebra di Boole, per cui il valore puoessere solamente true o false (vero o falso, 1 o 0); e vengono usati soprattutto negli enunciatidecisionali (che vedremo in seguito).
I principali metodi che ritornano un oggetto booelano, e che si trovano in gran parte delle classiesistenti, sono i metodi di comparazione:
• == uguale a
• < minore di
• > maggiore di
• <= minore o uguale a
• >= maggiore o uguale a
• != diverso da
ad esempio:
confronto1= 4==5
confronto2= 4<5
confronto3= "a">="c"
9
ovviamente, confronto1 sara false, confronto2 true e confronto3 false.
Nell’algebra booleana sono presenti gli operatori e, o e “negazione“; dunque anche i tipi di datobooleano dispongono di questi operatori:
• and - e
• or - o
• ! - “negazione“
vediamo qualche esempio:
e1= 3<4 and 4<5 #=> true
e2= !(3<4) #=> false
e3= 3<4 or 5>7 #=> true
e4= "a"<"d" and !(3>6) #=> true
e5= !("h"<"c" and "h"<"z") #=> true
e6= ((2>=5) or !("a"!="r")) and e4 #=> false
2.10 Enunciati decisionali - IF/UNLESS
L’enunciato if, o il suo negato unless viene adoperato nel caso si debbano eseguire delle istruzionisolo in presenza di una determinata condizione (oggetto booleano)Ad esempio:
if (2 < 3)
puts "minore!!!"
end
ne abbiamo anche aprofittato per vedere che ogni blocco di codice - in questo caso quello da eseguirenel caso la condizione sia vera - deve terminare con la parola chiave end.Se pero, come in questo caso, dobbiamo eseguire una sola istruzione, possiamo compattare tuttoin un’unica riga, evitando percio il blocco di codice:
puts "minore!!!" if (2 < 3)
Ora, nel caso volessimo eseguire il blocco di codice solo nel caso la condizione di controllo sia falsa,abbiamo due modi di procedere: 1) usando la negazione propria dell’algebra booleana; 2) usandola parola chiave unless. Vediamolo insieme:
if !(2 < 3)
puts "non minore"
end
e del tutto equivalente a
10
unless (2 < 3)
puts "non minore"
end
Come visto prima per l’if, se abbiamo una sola istruzione da eseguire, anche l’unless puo esserecompattato su una sola riga:
puts "non minore" unless (2 < 3)
Se alla condizione si vuole realizzare un’alternativa, si puo utilizzare la clausula else, che sta peraltrimenti :
if condizione_booleana
puts "condizione vera"
else
puts "condizione falsa"
end
in questo caso si chiude con l’end il blocco totale, comprensivo di else.La stessa cosa vale per unless:
unless condizione_booleana
puts "condizione falsa"
else
puts "condizione vera"
end
Nel caso di if e anche possibile aggiungere degli “step” intermedi; ipotizziamo di voler confrontaredue numeri:
if n1 < n2
puts "n1 minore di n2"
elsif n1 == n2
puts "n1 uguale a n2"
else
puts "n1 maggiore di n2"
end
2.11 Altro tipo di dato: gli array
Prima di vedere i cicli iterativi ci conviene dare uno sguardo agli array:un array e un insieme di oggetti, a livello di codice delimitati da [ e ]
ad esempio, per “creare” un oggetto di tipo array vuoto:
array=[]
11
mentre, per creare un array contentente gli oggetti-numeri 1, 2, 3 e 4
array=[1, 2, 3, 4]
Se ad un array esistente volessimo aggiungere in coda un altro elemento e sufficiente usare <<:
array << 6
array << 5
ora array sara [1, 2, 3, 4, 6, 5].
In realta << e un “sinonimo” del metodo push invocato su un array,quindi
array << 8
avra lo stesso effetto di
array.push 8
Se volessimo ordinare gli elementi al suo interno
array.sort!
array sara ora [1, 2, 3, 4, 5, 6, 8]
E possibile accedere ad uno specifico elemento di un array sempre con [], ricordandoci che ogni og-getto ha un “indice” che lo identifica, e che gli indici partono da 0 e arrivano a numero di elementi -1in questo caso:
primo_elemento=array[0]
primo elemento conterra quindi 1in maniera simile, nel caso volessimo sovrascriverne il valore
array[0]=-1
Un’altro oggetto particolare, simile agli array, e l’oggetto Range, (intervallo) che si definisce inquesto modo:
intervallo=(1..10)
dove intervallo rappresenta ora tutti i numeri da 1 a 10.Se ora volessimo avere un array contenente tutti questi numeri basterebbe invocare il metodo.to a sul range:
array=(1..10).to_a
e possibile una cosa simile anche con caratteri:
array=("a".."d").to_a
array sara quindi [’a’, ’b’, ’c’, ’d’]
Un array puo contenere al suo interno qualsiasi tipo di oggetto (quindi anche un array)
12
2.12 Cicli di istruzioni
2.12.1 while
Il ciclo while serve a ripetere delle istruzioni (blocco di codice) finche una condizione e vera
count = 0
while count < 10
puts "count = #{count.to_s}"
count = count+1
end
2.12.2 for
Il ciclo for, a differenza del while, esegue delle istruzioni per un determinato numero di oggetti(array o range):
array=[1, 2, 3, 4, 5]
for n in array
puts "Numero: #{n.to_s}"
end
2.12.3 blocco each - blocco times
Tuttavia esiste un modo piu efficace per ottenere il risultato di un ciclo for:tutti gli oggetti di tipo array o range possiedono il metodo each.Il metodo each e un metodo di tipo particolare, esso infatti non richiede parametri in ingresso,ma richiede che dopo la sua invocazione sia presente un blocco di codice da eseguire:eccone un esempio:
array=[1, 2, 3, 4, 5]
array.each do |n|
puts "Numero: #{n.to_s}"
end
simile al metodo each di un array/range, e il metodo times dei numeri interi:
5.times do |n|
puts "Numero: #{n.to_s}"
end
A volte nella documentazione si trova qualcosa di simile a
array.each { |n|
puts n
}
13
o meglio ancora
array.each {|n| puts n }
ma il significato e sempre lo stesso.
Personalmente trovo sia piu leggibile delimitare il blocco con do e end.
2.13 Ancora array...
Sempre per restare in tema di array vediamo altri metodi utili, invocabili su array:
• to s - converte un array in stringa
[1, 2, 3].to_s #=> "123"
• join - simile a to s, ma permette di definire una stringa da inserire tra i vari oggetti in fasedi concatenazione
[’a’, ’b’, ’c’].join(", ") #=> "a, b, c"
[’a’, ’b’, ’c’].join("--") #=> "a--b--c"
• push - vista prima, inserisce un oggetto in coda ad un array
array=[1,2,3]
array.push 4 #=> [1,2,3,4]
• pop - estrae dall’array l’ultimo elemento
array=[1,2,3]
ultimo=array.pop # (array vale ora [1,2] e ultimo vale 3)
• first - ritorna il primo elemento dell’array, senza estrarlo
[1,2,3].first #=> 1
• last - ritorna l’ultimo elemento dell’array, senza estrarlo
[1,2,3].last #=> 3
• length o size - ritorna la dimensione di un array
[1,2,3].size #=> 3
• sort e sort! - ricordi il ! nelle funzioni?la prima ritorna un nuovo array, contenente gli elementi dell’array di partenza ordinati; laseconda ordina in-place
ar1=[3, 5, 1]
ar2=ar1.sort # (ar2 vale [1, 3, 5], mentre ar1 resta invariato)
ar1.sort! # (ar1 vale [1, 3, 5])
• include? - ritorna un valore booleano, vero se l’elemento cercato e incluso, altrimenti falso
14
ar1=[1, 2, 3]
ar1.include? 2 #=> true
ar1.include? 8 #=> false
altri metodi consultabili su
http://www.ruby-doc.org/core/classes/Array.html
2.14 Ancora stringhe...
Vediamo ora alcuni metodi utili invocabili su una stringa:
• split - si puo considerare come l’inverso di join applicato ad un array, infatti divide lastringa in elementi di un array
"a--b--c".split("--") #=> ["a", "b", "c"]
• [] - e una funzione particolare che permette di accedere a determinati caratteri della stringa.Se invocata con un numero ritorna il byte relativo, mentre se invocata con un “range” ritornala serie di caratteri relativa:
a = "hello there"
a[1] #=> 101 (byte in posizione 1)
a[1].chr #=> "e" (byte in posizione 1 convertito in carattere)
a[1,3] #=> "ell" (3 caratteri partendo dalla posizione 1 compresa)
a[2,3] #=> "llo" (3 caratteri partendo dalla posizione 2 compresa)
a[1..3] #=> "ell" (caratteri dalla posizione 1 alla posizione 3)
a[-3,2] #=> "er" (2 caratteri dalla posizione 3 partendo da destra)
a[-4..-2] #=> "her" (dalla posizione -4 alla -2)
• []= - cambia un determinato carattere della stringa:
a = "hello there"
a[0]="H" #=> a vale "Hello there"
• <=> - serve a comparare una stringa con un’altra; ritorna -1 se la stringa viene prima(alfabeticamente parlando), 0 se equivalenti, 1 se viene dopo
"abc"<=>"cba" #=> -1
"ciao"<=>"ciao" #=> 0
"ciao"<=>"bao" #=> 1
• + - l’abbiamo gia visto, e l’operatore di concatenazione
"ci"+"ao" #=> "ciao"
• * - ripete la stringa per n volte
saluto="ciao " * 3 #=> "ciao ciao ciao "
15
• capitalize e capitalize! - fanno diventare maiuscola la prima lettera, la prima crea unnuovo oggetto, la seconda modifica l’oggetto esistente
a = "ciao"
b=a.capitalize # (b vale "Ciao", a vale ancora "ciao")
a.capitalize! # (a vale "Ciao")
• chomp e chomp! - rimuove carattere di fine linea, la prima crea un nuovo oggetto, la secondomodifica l’oggetto esistente
a = "ciao\n"
b=a.chomp # (b vale "ciao", a vale ancora "ciao\n")
a.chomp! # (a vale "ciao")
• size - determina la lunghezza della stringa
"ciao".size #=> 4
• downcase e downcase! - trasformano tutti i caratteri maiuscoli in minuscoli, la prima creaun nuovo oggetto, la secondo modifica l’oggetto esistente
a = "CIAO"
b=a.downcase # (b vale "ciao", a vale ancora "CIAO")
a.downcase! # (a vale "ciao")
• empty? - ritorna true se la stringa e vuota
"".empty? #=> true
"c".empty? #=> false
• include? - ritorna true se la stringa include una sequenza di caratteri
"ciao".include? "ao" #=> true
"ciao".include? "bo" #=> false
• index - ritorna la posizione della prima occorrenza di una stringa in quella di partenza
"ciao".index "a" #=> 2
"ciao".index "ia" #=> 1
• reverse e reverse! - serve a invertire i caratteri di una stringa, la prima crea un nuovooggetto, la secondo modifica l’oggetto esistente
"stefano".reverse #=> "onafets"
• upcase e upcase! - trasformano tutti i caratteri minuscoli in maiuscoli, la prima crea unnuovo oggetto, la secondo modifica l’oggetto esistente
a = "ciao"
b=a.upcase # (b vale "CIAO", a vale ancora "ciao")
a.upcase! # (a vale "CIAO")
altri metodi consultabili su
http://www.ruby-doc.org/core/classes/String.html
16
2.15 Definizione di metodi
Abbiamo visto che le strutture iterative sono molto utili per evitare di dover scrivere codiceripetitivo, un altro modo per evitare di scrivere codice ripetitivo e definire dei propri metodi (ofunzioni).partiamo per esempi:ipotizziamo il seguente programma:
nome="Mirko"
puts "Ciao, #{nome}!"
nome="Stefano"
puts "Ciao, #{nome}!"
vediamo chiaramente che e presente una parte ripetitiva.Possiamo semplificarci la vita creando il metodo saluta, che accetta come parametro in entrataun oggetto da stampare con puts:
def saluta(nome)
puts "Ciao, #{nome}!"
end
saluta(’Stefano’)
saluta ’Mirko’
Partiamo con l’analisi:
un metodo e un blocco di codice, che comincia con
def <nome_del_metodo>(<variabili_in_ingresso>)
ed essendo un blocco deve terminare con end.
All’interno possono starci delle istruzioni standard o chiamate ad altri metodi.Come vediamo, dall’istruzione puts interna al metodo e usata la variabile nome, che e appuntoquella definita in ingresso.
Ora, come possiamo ben vedere, quello che abbiamo scritto e un metodo che non restituisce alcunvalore in uscita.Come fare quindi, se vogliamo far uscire qualcosa da quel metodo?Vediamo questo metodo un po’ piu complesso che, dato un array di numeri interi torna in uscitala somma di tutti i valori presenti:
def somma_valori(array)
somma=0
array.each do |elemento|
somma=somma+elemento
end
return somma
17
end
somma=somma_valori([1, 2, 3, 4])
puts somma
notiamo subito prima dell’end l’istruzione return somma, questa e l’istruzione che serve perritornare un valore al programma chiamante.
A volte l’istruzione return puo anche essere omessa, in quanto, se tale, ruby ritornera per defaultil valore dell’ultima assegnazione o chiamata a funzione.Ad esempio, molto banalmente
def banale
a=1
end
puts banale
stampera a video 1
Nel nostro caso quindi avemo potuto scrivere
def somma_valori(array)
somma=0
array.each do |elemento|
somma=somma+elemento
end
somma
end
attenzione: il somma prima di end corrisponde ad un’assegnazione a vuoto, proprio per direa ruby che il valore da tenere nel buffer da ritornare e il valore di somma, altrimenti avrebberitornato l’array di partenza, perche l’ultima istruzione sarebbe stata array.each (che lavoraquindi sull’array di partenza) [in questo caso il blocco viene visto come istruzione unica!!!]
Ma allora, se e possibile farlo implicitamente, perche usare l’istruzione return?Perche il return forza l’uscita dal metodo. Ad esempio:
def prova123(numero)
if numero < 10
return "si"
end
"no"
end
puts prova123(12) => ’no’
puts prova123(9) => ’si’
18
omettendo il return invece avrebbe scritto ’no’ anche per il 9, perche l’esecuzione del metodosarebbe continuata, ottenendo cosı come ultimo valore ’no’ in ogni caso.
A un metodo e possibile passare piu di un parametro in ingresso, ad esempio:
def confronto(a, b)
if a<b
puts "a < b"
elsif a==b
puts "a = b"
else
puts "a > b"
end
end
ma nonostante un metodo possa prevedere piu di un parametro in ingresso puo ritornare un solooggetto. Tuttavia, nel caso sentissimo la necessita di tornare piu di un oggetto in uscita possiamoapplicare un piccolo trucco: ritornare un array di oggetti, e usare l’assegnazione multipla:
def ritorno_multiplo
[1, ’ciao’, [1, 2]]
end
numero, stringa, array = ritorno_multiplo
numero ora vale 1, stringa vale ’ciao’ e array vale [1, 2].
2.16 Semplici operazioni con file di testo
2.16.1 lettura di un file
In fase di lettura tutto il file viene caricato come stringa unica, sta poi all’utente recuperare levarie righe.Il separatore di riga e sempre ritorno a capo, carattere speciale “\n“, quindi basta uno split, o,piu semplicemente, each line.
contenuto = File.read ’prova.txt’
# ora leggo le righe con uno split: (righe sara‘ un array)
righe = contenuto.split("\n")
# oppure con each_line:
contenuto.each_line do |riga|
...
end
19
2.16.2 scrittura di un file
La scrittura di un file di testo e leggermente piu complicata, in quanto richiede un blocco di codice:
File.open ’prova123.txt’, ’w’ do |f|
f.write "Ciao, sono la riga 1\n" # ricordiamoci il ritorno a capo!
f.write "Ciao, sono la riga 2"
end
2.16.3 lavorare con il filesystem
Vediamo ora alcune funzioni utili per poter lavorare con il filesystem:
• directory listing (con array)
files_txt_directory_corrente=Dir[’*.txt’]
files_immagine_ricorsivi=Dir[’**/*.{JPG,jpg}’]
• spostamento in una directory
Dir.chdir ’/home/stefano/prove_ruby’
• rinominare un file
File.rename ’vecchio_file.txt’, ’nuovo_file.txt’
• cambiare i permessi di un file (solo Unix)
File.chmod(0644, "testfile.txt")
• creazione di una directory
Dir.mkdir ’dir_1’
Dir.mkdir ’dir_2’, 0644
• directory corrente
current_directory=Dir.pwd
• copia di un file
File.copy ’file_sorgente.txt’, ’file_destinazione.txt’
per altri metodi vedere
http://ruby-doc.org/core/classes/File.htmlhttp://ruby-doc.org/core/classes/Dir.html
20
2.17 Altri oggetti utili
Vediamo ora altri oggetti che possono essere utili:come introduzione e necessario sapere che per avere una nuova istanza di un oggetto bisognachiamare la classe stessa con il metodo new.
Finora non e stato visto perche ruby introduce delle semplificazioni nei tipi di dato nativi,ad esempio
stringa=""
array=[]
e equivalente a
stringa=String.new
array=Array.new
2.17.1 La classe ’Time’
Rappresenta una data comprensiva di giorno, mese, anno e orario:
adesso=Time.new
adesso_piu_un_minuto=adesso+60
puts adesso #=> Tue Sep 09 11:54:45 CEST 2008
puts adesso_piu_un_minuto #=> Tue Sep 09 11:55:45 CEST 2008
praticamente con l’operatore + vengono sommati i secondi.
Se si vuole creare un oggetto Time per uno specifico momento si puo usare mktime:
puts Time.mktime(2000, 1, 1) #=> Sat Jan 01 00:00:00 CET 2000
puts Time.mktime(1986, 11, 21, 18, 30) #=> Fri Nov 21 18:30:00 CET 1986
ovviamente un oggetto Time ha tutta una serie di metodi di utilita; vediamone brevemente alcunicon un esempio:
adesso=Time.new #=> Tue Sep 09 11:54:45 CEST 2008
giorno=adesso.day #=> 9
mese=adesso.month #=> 9
anno=adesso.year #=> 2008
Come ogni classe, l’elenco completo dei metodi e disponibile a
http://ruby-doc.org/core/classes/Time.html
21
2.17.2 La classe ’Hash’
E disponibile anche qui, come array, string, ... un costrutture semplificato,
hash=Hash.new
e infatti equivalente a
hash={}
Un hash e molto simile ad un’array, con la differenza che gli indici possono essere degli oggetti.Vediamola molto simile ad un dizionario, in cui ad una parola corrisponde una definizione.ad esempio:
hash={}
hash[’mirko’]=’porseo’
hash[’stefano’]=’nerd’
Anche come metodi e molto simile ad un array:
http://ruby-doc.org/core/classes/Hash.html
22
Capitolo 3
Programmazione orientata agli
oggetti
3.1 Definizione di classe e oggetto
Un oggetto e una “scatola”, con le proprie caratteristiche e i propri comportamenti;ad esempio, un router ha marca e modello, e fa’ determinate cose (connettersi, disconnettersi, ...).Questo puo essere visto come un “oggetto”Informaticamente parlando, per poter creare un tipo di oggetto occorre definire una classe che nedefinisce i metodi e gli attributi.Diminuendo il grado di astrazione e tornando alla programmazione vera e propria possiamo dire cheun oggetto puo essere visto come un contenitore di dati (che a questo punto possiamo identificarecon delle semplici variabili) dotato di una serie di funzioni (i metodi) che definiscono un’interfacciaall’oggetto stesso (azioni da compiersi sull’oggetto).
3.2 Creazione di classi personalizzate
3.2.1 costruzione di una classe
Vediamo subito come e implementata in Ruby la programmazione orientata agli oggetti. Scriviamoun semplice esempio supponendo di dover programmare un videogame di guerra dove tra le altrecose sono presenti dei carri armati e dei camion per il trasporto dei soldati. Rappresentiamo questioggetti in Ruby:
class CarroArmato
def initialize
puts "Sono un nuovo carro armato"
end
end
23
class Camion
def initialize
puts "Sono un nuovo camion"
end
end
ca = CarroArmato.new #=> Sono un nuovo carro armato
c = Camion.new #=> Sono un nuovo camion
abbiamo quindi capito che la definizione di una classe inizia con la parola chiave class e terminacon end; initialize e il metodo che viene invocato in fase di creazione di un oggetto (vienechiamato costruttore).
In questo caso, molto banalmente, il costruttore stampa a video l’avvenuta creazione dell’oggetto.
Vediamo ora invece come sia possibile passare dei dati al costruttore; supponiamo quindi di vo-ler definire alcuni dati caratteristici dell’oggetto (carroarmato I carburante e colpi; camion I
carburante e posti passeggeri):
class CarroArmato
def initialize (colpi, carburante)
@carburante = carburante
@colpi = colpi
end
end
class Camion
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
end
3.2.2 variabili di istanza
Ma cosa sono quella specie di variabili il cui nome comincia per @?Vengono chiamate variabili di instanza, e una volta inizializzate esse sono visibili a tutti i metodidella classe, e verranno conservate per tutta l’esistenza del singolo oggetto.capiremo poi il motivo della loro importanza.
Ora quindi possiamo creare un’istanza di carroarmato e una di camion:
ca = CarroArmato.new(10, 100)
c = Camion.new(20, 100)
le variabili di istanza @carburante, @posti, @colpi, ... vengono anche detti attributi della classe,e definiscono i dati fondamentali su cui si basa la stessa.
24
Per accedere dall’esterno della classe a questi attributi dobbiamo definire degli appositi metodi;ad esempio, per fare in modo di poter capire quanti posti sono presenti nel camion
class Camion
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
def posti
@posti
end
end
da notare che il metodo posti si sarebbe anche potuto scrivere (come abbiamo gia visto)
def posti
return @posti
end
(ovviamente sempre all’interno del blocco di codice che definisce la classe)quindi, potremo
c = Camion.new(20, 100)
puts c.posti #=> 20
Abbiamo visto ora come accedere agli attributi di una classe in lettura; anche nel caso volessimoaccederci in scrittura e necessario definire un metodo;rivediamo sempre la classe Camion, con la possibilita di modificare il numero di posti a disposizione:
class Camion
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
def posti
@posti
end
def posti=(nuovi_posti)
@posti=nuovi_posti
end
end
il metodo per accedere in scrittura non e nient’altro che un semplicissimo metodo con un parametroin ingresso. Riproviamo quindi ad eseguire:
25
c = Camion.new(20, 100)
puts c.posti #=> 20
c.posti=25
# le parentesi tonde () sono facoltative,
# scrivere c.posti=(25) e‘ uguale a c.posti=25
puts c.posti #=> 25
E qui ruby ci viene incontro ancora una volta...
Ipotizziamo voler avere 50 variabili che devono essere accessibili dall’esterno della classe sia in scrit-tura che in lettura... dovremmo quindi scrivere per 50 volte i 2 metodi, variabile, e variabile=...ruby ci viene in aiuto con una determinata parola chiave che ci permette di definire automatica-mente questi due metodi per ogni variabile che vogliamo venga trattata:vediamolo sempre con la classe Camion:
class Camion
attr_accessor :posti
attr_accessor :carburante
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
end
e proprio la “parola” attr accessor che permette di definire automaticamente questi due metodiper ogni variabile specificata.
Da notare che per ridurre ancora di piu il codice scritto, invece di
attr_accessor :posti
attr_accessor :carburante
avremmo potuto scrivere
attr_accessor :posti, :carburante
ma... se volessimo che una variabile, chiamata sola lettura fosse accessibile in sola lettura, eun’altra viariabile chiamata sola scrittura fosse accessibile in sola scrittura? Anche in questocaso ruby ci viene in aiuto con due costrutti:
attr_reader :sola_lettura
attr_writer :sola_scrittura
da notare che, praticamente, scrivere
attr_accessor :variabile
26
corrisponde esattamente a scrivere
attr_reader :variabile
attr_writer :variabile
(ovviamente il primo caso e molto piu pratico)
Vediamone ora un uso pratico delle variabili di istanza, che non sia il semplice reperimento/impostazione;sempre riguardo la classe Camion:
class Camion
attr_reader :posti
attr_reader :carburante
def initialize (posti, carburante)
@carburante = carburante
@posti = posti
end
def rifornimento (quantita)
@carburante = @carburante + quantita
end
end
Vediamo che il metodo rifornimento va a modificare la variabile di instanza @carburante
c = Camion.new(20, 100)
puts c.posti #=> 20
puts c.carburante #=> 100
c.rifornimento 50
puts c.carburante #=> 150
Ricapitolando un attimo sulle variabili, abbiamo ora capito che ci sono 2 ambiti di visibilita dellestesse:
le variabili che iniziano per @ sono visibili a tutta la classe in cui sono definite,mentre le variabili senza @ sono visibili solo all’interno dei metodi in cui sono definite.
3.2.3 ereditarieta
Ora pero, pensandoci bene, entrambi i due mezzi (camion e carroarmato) sono dei veicoli, e cometale hanno delle cose in comune (ad esempio il carburante);e percio possibile definire una “macro-classe” che li contiene entrambi, quindi possiamo scrivere
27
class Veicolo
attr_reader :carburante
def initialize (carburante)
@carburante = carburante
end
def rifornimento (quantita)
@carburante += quantita
end
end
e poi far “discendere” camion e carroarmato direttamente da veicolo, in modo che ne ereditino leproprieta:
class CarroArmato < Veicolo
attr_reader :colpi
def initialize (carburante, colpi)
super(carburante)
@colpi = colpi
end
end
class Camion < Veicolo
attr_reader :posti
def initialize (carburante, posti)
super(carburante)
@posti = posti
end
end
Il simbolo < sta per discende da, o eredita da,mentre il metodo super serve per chiamare il metodo con lo stesso nome ma della classe genitore.In questo caso, super richiama initialize del genitore.
Ovviamente questo ci fa risparimare un bel po’ di codice scritto, e ci puo semplificare di moltola vita... Ipotizziamo di avere 100 classi, e tutte queste classi hanno un metodo uguale. Dovendomodificare la logica di quel metodo, avremo bisogno di andare a modificare ognuna delle 100 classiprecedentemente definite. Utilizzando l’ereditarieta invece, la modifica necessaria si riduce ad unasola classe.
3.3 Modifica di classi esistenti
A volte, una cosa utile puo essere modificare una classe esistente.
28
Supponiamo di avere un array di numeri, di cui calcolare la somma, oppure la media.Certo con un each si puo fare tutto, ma se il numero di array aumenta bisognerebbe scrivere uneach per ogni array...ci farebbe molto comodo se per ogni oggetto array esistessero i metodi sum e avg... in ruby perodi default non esistono.Possiamo pero crearli noi andando a modificare al volo la classe Array! Basta semplicemente:
class Array
def sum
s=0
self.each do |x|
s=s+x
end
s
end
def avg
s=sum.to_f
l=self.size.to_f
s/l
end
end
somma=[1, 2, 3, 4, 5].sum #=> 15
media=[1, 2, 3, 4, 5].avg #=> 3
3.4 Gestione degli errori - Eccezioni
Un programma in esecuzione puo passare attraverso vari problemi inaspettati. Un file che vorrebbeleggere potrebbe non esistere; il disco potrebbe essere pieno quando desidera salvare dei dati;l’utente potrebbe fornirgli dei dati inaccettabili.
Un programma robusto gestira tali situazioni sensibilmente e in maniera elegante.
In ruby, come in molti linguaggi moderni, possiamo gestire le eccezioni per blocchi di codice inmodo compartimentato, ottenendo un risultato sorprendentemente efficiente ma senza caricare inmodo eccessivo il programmatore o chiunque tenti di leggere il codice in seguito. Il blocco dicodice segnalato con begin viene eseguito finche non c’e un’eccezione, che causa il trasferimentodel controllo ad un blocco di gestione dell’eccezione, che viene segnalato da rescue. Se non c’e uneccezione, il codice rescue non viene usato. Il metodo seguente ad esempio restituisce la primalinea di un file di testo, o nil se c’e un eccezione:
def prima_linea(filename)
begin
file = open("un_file")
info = file.gets
file.close
29
info
rescue
nil
end
end
E necessario sapere che anche un’eccezione di per se e un oggetto come un altro (discendente diException), ed essendo tale e percio possibile distinguere il tipo di errore sollevato, e agire diconseguenza.Potremo quindi voler scrivere
begin
...codice...
rescue TipoDiEccezione1
...azione1...
rescue TipoDiEccezione2
...azione2...
end
ed e anche possibile recuperare e stampare a video in maniera piu gradevole il tipo di errore:
begin
...codice...
rescue Exception => e
puts e.message
end
Volendo potremmo sollevare un’eccezione da un punto qualsiasi del nostro programma, ad esempio:
def inverse(x)
raise ArgumentError, ’Argument is not numeric’ unless x.is_a? Numeric
1.0 / x
end
La gerarchia delle eccezioni e questa:
• Exception
– NoMemoryError
– ScriptError
∗ LoadError
∗ NotImplementedError
∗ SyntaxError
– SignalException
∗ Interrupt
30
– StandardError
∗ ArgumentError
∗ IOError
· EOFError
∗ IndexError
∗ LocalJumpError
∗ NameError
· NoMethodError
∗ RangeError
· FloatDomainError
∗ RegexpError
∗ RuntimeError
∗ SecurityError
∗ SystemCallError
∗ SystemStackError
∗ ThreadError
∗ TypeError
∗ ZeroDivisionError
– SystemExit
31
Capitolo 4
Uso di librerie particolari
4.1 Gemme
Le librerie di ruby come potrebbero chiamarsi se non, per l’appunto, gemme?
Alcune gemme (le piu usate) sono gia incluse nell’installazione standard di ruby, mentre per altree necessaria l’installazione manuale. Per questo ci viene in aiuto il comando gem.
Ad esempio
~# gem install mysql
installa la gemma per accedere a database mysql.
4.2 Lavorare con indirizzi IP
Esiste una classe molto comoda che ci permette di trattare indirizzi IP:
# Include la libreria ’ipaddr’
require ’ipaddr’
# 1- indirizzo in notazione decimale puntata
IPAddr.new(’192.168.1.1’)
# 2- indirizzo + netmask in notazione decimale puntata
IPAddr.new(’192.168.1.1/255.255.255.255’)
# 3- indirizzo in notazione decimale puntata + CIDR
IPAddr.new(’192.168.1.1/32’)
# 4- verifica se un indirizzo appartiene a una determinata sottorete
network=IPAddr.new(’192.168.0.0/23’) # <IPAddr: IPv4:192.168.0.0/255.255.254.0>
32
network.include?(IPAddr.new(’192.168.0.23’)) #=> true
network.include?(IPAddr.new(’192.168.1.1’)) #=> true
network.include?(IPAddr.new(’192.168.10.1’)) #=> false
network.include?(IPAddr.new(’172.16.100.2’)) #=> false
4.3 YAML
YAML1 e un formato che permette di salvare come stringhe/file di testo degli oggetti ruby, insom-ma, una specie di XML.Vediamo un esempio rapido:
# inclusione della libreria YAML
require ’yaml’
test_array = [’Elemento 1’ ,
’Elemento 2’ ,
’Elemento 3’ ]
test_string = test_array.to_yaml
filename = ’output_yaml.txt’
File.open filename, ’w’ do |f|
f.write test_string
end
read_string = File.read filename
read_array = YAML::load read_string
puts(read_string == test_string) #=> true
puts(read_array == test_array ) #=> true
4.4 Inviare mail via SMTP
Utilizzando la libreria net/smtp e molto facile inviare una e-mail:
require ’net/smtp’
user_from = "[email protected]"
user_to = "[email protected]"
email_body = "From: [email protected]\n"
email_body = email_body + "Subject: Ciao\n\nEmail da Ruby.\n\n"
1http://en.wikipedia.org/wiki/YAML
33
# gestione delle eccezioni
begin
Net::SMTP.start(’my.smtp.server’, 25,) do |smtpclient|
smtpclient.send_message(email_body, user_from, user_to)
end
rescue Exception => e # rescue e‘ utilizzato per gestire le eccezioni
print "Eccezione verificata: " + e
end
4.5 Accesso a database MySQL
E necessaria la gemma mysql
Vediamone l’uso per esempi:
# libreria mysql
require ’mysql’
# stabilisce connessione con il server
con=Mysql.new(’localhost’, ’username’, ’password’, ’database_name’)
# query senza risultati di ritorno:
con.query("CREATE TABLE prova (
name VARCHAR(40),
email VARCHAR(40)
)")
con.query("DROP TABLE IF EXISTS prova1234")
con.query("INSERT INTO prova (name, email) VALUES
(’tizio’, ’[email protected]’),
(’caio’, ’[email protected]’),
(’sempronio’, ’[email protected]’)
)" )
numero_righe_inserite=con.affected_rows # => 3
# certo che pero‘ effettuare inserimenti cosi‘
# o interpolando stringhe in una query
# puo‘ risultare pericoloso...
# meglio inserire i caratteri di escape...
email=con.escape_string("[email protected]")
con.query("UPDATE prova SET email = ’#{email}’ WHERE name = ’sempronio’")
numero_righe_inserite=con.affected_rows # => 1
34
# query con record di ritorno (SELECT)
rs=con.query(’select * from prova’)
numero_di_righe=rs.num_rows
# ora abbiamo due modi per scorrere un insieme di record (recordset):
# 1) ciclo while
while row = rs.fetch_hash do
puts "Nome: " + row[’name’]
puts "E-Mail: " + row[’email’]
puts "------"
end
# 2) each
rs.each_hash do |row|
puts "Nome: " + row[’name’]
puts "E-Mail: " + row[’email’]
puts "------"
end
# 3) each, includento nel come del campo anche il nome della tabella
rs.each_hash(with_table=true) do |row|
puts "Nome: " + row[’prova.name’]
puts "E-Mail: " + row[’prova.email’]
puts "------"
end
# liberiamo la ram utilizzata dal recordset
rs.free
# e ora chiudiamo la connessione
con.close
4.6 Accesso a database SQLite
E necessaria la gemma sqlite3
Un database SQLite e molto piu facile da manipolare che non MySQL:
require ’sqlite3’
db = SQLite3::Database.new("book.db")
db.execute("SELECT * FROM libro") do |riga|
puts "#{riga.titolo} - #{riga.autore}"
end
35
# se invece siamo interessati solo alla prima riga:
prima_riga = db.get_first_row( "SELECT * FROM libro" )
db.close
4.7 Accesso a database KirbyBase
KirbyBase e un DBMS molto leggero e interamente scritto in Ruby. I dati vengono salvati sufile di testo e possono essere eventualmente modificati a mano, e possibile usarlo sia in modalitaembedded sia in modalita client/server. Puo essere visto come una via di mezzo tra i file di testosemplice e i database di tipo SQL.
Partiamo con la creazione di un database e di una tabella:
# richiesta la libreria base delle gemme
require ’rubygems’
# libreria kirbybase
require ’kirbybase’
db = KirbyBase.new
book = db.create_table(:libro,
:titolo, :String,
:autore, :String,
:anno, :Integer
)
KirbyBase.new crea un database nella directory corrente, se pero volessimo utilizzare un’altradirectory per immagazzinare i dati sarebbe sufficiente dare
db = KirbyBase.new(:local, nil, nil, ’./data’)
Il metodo create table prende come argomenti il nome della tabella e il nome e il tipo di ognicolonna. I tipi disponibili sono: String, Integer, Float, Boolean, Time, Date, DateTime, Memo,Blob, e YAML. Per ogni campo e possibile specificare anche alcune proprieta come il valore didefault, l’obbligatorieta del campo, l’indicizzazione e riferimenti ad altri record in altre tabella.
E’ anche possibile cifrare la tabella, utilizzando
book = db.create_table(:libro, ...) do |t|
t.encrypt = true
end
Per popolare la nostra tabella ora si usa il metodo insert:
36
book.insert("Reduce","Giovanni Lindo Ferretti",2006)
#=> 1
book.insert("Il bosco degli urogalli","Mario Rigoni Stern",1962)
#=> 2
(il numero ritornato e il valore della chiave primaria)in alternativa e possibile usare dei blocchi di codice:
book.insert do |l|
l.titolo = "Memorie dal sottosuolo"
l.autore = "Fedor Dostoevskij"
l.anno = 1864
end
#=> 3
se ora volessimo ricavare tutto il contenuto della tabella basta utilizzare il metodo select senzanessun parametro:
ris = book.select
#=> [#<struct #<Class:0xb7a98f14> recno=1, titolo="Reduce", autore="Giovanni Lindo Ferretti", anno=2006>, #<struct #<Class:0xb7a98f14> recno=2, titolo="Il bosco degli urogalli", autore="Mario Rigoni Stern", anno=1962>, #<struct #<Class:0xb7a98f14> recno=3, titolo="Memorie dal sottosuolo", autore="Fedor Dostoevskij", anno=1864>]
ris e un oggetto di tipo KBResultSet.Se invece si vogliono visualizzare solo alcune colonne basta specificarle come parametro:
ris_titolo = book.select(:titolo)
#=> [#<struct #<Class:0xb79ac8d0> titolo="Reduce">, #<struct #<Class:0xb79ac8d0> titolo="Il bosco degli urogalli">, #<struct #<Class:0xb79ac8d0> titolo="Memorie dal sottosuolo">]
Per meglio definire la ricerca e possibile usare dei blocchi di codice:
ris_duemila = book.select(:titolo) do |l|
l.anno > 2000
end
#=> [#<struct #<Class:0xb79a6638> titolo="Reduce">]
I primi passi che abbiamo visto sono necessari a creare la tabella; se invece la nostra tabella esistegia, e vogliamo soltanto un riferimento che punta ad essa:
tabella = db.get_table(:libro)
Per i vari dettagli e altre operazioni rimando a
http://www.netpromi.com/files/kirbybase ruby manual.html
4.8 ActiveRecord
**** DA FARE ****
37
4.9 File XML
Vedremo ora come utilizzare due diverse librerie per trattare file XML, capiremo come leggerli ecome costruirli.Le librerie si chiamano xmlsimple e rexml. La prima permette di convertire un file xml in unhash e viceversa. La seconda invece va a lavorare direttamente con la struttura DOM2 del file.
4.9.1 xmlsimple
E richiesta la gemma xml-simple.
lettura di un file
require ’rubygems’
require ’xmlsimple’
xmlfile = File.new("cd.xml")
doc = XmlSimple.xml_in xmlfile
Come detto prima, doc sara un semplice oggetto di tipo Hash.
creazione di un file
require ’rubygems’
require ’xmlsimple’
miohash={}
miohash[’root’]={}
miohash[’root’][’dir1’]="Prova 123"
miohash[’root’][’dir2’]="Prova 124"
miohash[’root’][’k’]=[]
miohash[’root’][’k’][0]={"ciao"=>"bao1"}
miohash[’root’][’k’][1]={"ciao"=>"bao2"}
doc = XmlSimple.xml_out miohash
doc sara una stringa contenente il nostro codice xml, che in questo caso sara:
<opt>
<root dir1="Prova 123" dir2="Prova 124">
<k ciao="bao1" />
<k ciao="bao2" />
2DOM: Document Object Model
38
</root>
</opt>
4.9.2 rexml
Questa libreria offre un trattamento piu avanzato rispetto a xmlsimple.
lettura di un file
Ipotizziamo un file di esempio, cd.xml cosı formato:
<negozio nome="CD House">
<genere nome="Stoner">
<cd asin="B00000JBDE">
<titolo>Frequencies From Planet Ten</titolo>
<autore>Orange Goblin</autore>
<anno>1997</anno>
</cd>
<cd asin="B000WM72FC">
<titolo>Witchcult Today</titolo>
<autore>Electric Wizard</autore>
<anno>2007</anno>
</cd>
</genere>
<genere nome="Colonne sonore">
<cd asin="B00005O6PA">
<titolo>Amelie</titolo>
<autore>Yann Tiersen</autore>
<anno>2001</anno>
</cd>
</genere>
</negozio>
per leggerlo avremo bisogno di un programma simile al seguente:
require ’rexml/document’
xmlfile = File.new("cd.xml")
doc = REXML::Document.new(xmlfile)
doc.root.each_element do |genere|
puts genere.attributes["nome"]
genere.each_element do |cd|
cd.each_element do |val|
puts " #{val.name}: #{val.text}"
39
end
end
end
creazione di un file
require ’rexml/document’
# creiamo ora un documento xml, con tag radice <doc/>
doc=REXML::Document.new("<doc/>")
# creiamo ora un elemento example:
el = REXML::Element.new "example"
# con il testo "esempio 1"
el.text="esempio 1"
# e un attributo data
el.attributes["data"]="20081001"
# e lo aggiungiamo alla radice:
rt = doc.root
rt << el
# vediamo il risultato
puts doc.to_s #=> <doc><example data=’20081001’>esempio 1</example></doc>
*** DA SISTEMARE ***
4.10 Networking a basso livello
In Ruby il networking a basso livello e gestito dalla libreria standard socket che e strutturatanel seguente modo: alla base c’e BasicSocket classe astratta, sottoclasse di IO, che contienealcuni metodi fondamentali, ereditati da tutte le sue sottoclassi, come ad esempio close read,close write, getpeername, getsockname, recv e send.
Da BasicSocket discendono direttamente IPSocket, Socket e UNIXSocket. IPSocket e la classeche implementa le socket che usano il protocollo di trasporto IP e ha due sottoclassi: TCPSockete UDPSocket che rispettivamente trattano connessioni da, e verso, socket TCP e UDP. La classeSocket fornisce invece direttamente accesso all’implementazione delle socket del sistema operativo.UnixSocket infine gestisce le comunicazioni IPC utilizzando lo UNIX domain protocol.
Tutte le classi di socket discendono indirettamente dalla classe IO, questo vuol dire che e possibileusare i metodi di IO sui socket cosı come accade ad esempio per i file.
40
socket UDP
Iniziamo con un semplice esempio client-server, in cui il server inviera al client che si connetteraora e data corrente.
Server:
require "socket"
server = UDPSocket.open
server.bind(nil, 12345)
loop do
data, sender = server.recvfrom(1)
chost = sender[3]
cport = sender[1]
retstring = "Data corrente: #{Time.new.to_s}\n"
puts "Request from #{chost}:#{cport}"
server.send(retstring, 0, chost, cport)
end
Innanzitutto creiamo una nuova istanza di UDPSocket, e al mettiamo in ascolto sulla porta 12345(ovviamente UDP).Il loop principale non fa altro che rimanere in attesa di una connessione, e quando avviene inviail messaggio con la data
Client:
require "socket"
client = UDPSocket.open
client.connect(’localhost’, 12345)
client.send("", 0)
while msg=client.gets
puts msg
end
In questo caso creiamo una connessione verso localhost, e inviamo una stringa vuota al server(avremo potuto inviare qualsiasi cosa, tanto al nostro server non importa nulla). Infine ci mettiamoin ascolto per la risposta da parte del server e la stampiamo a video.
socket TCP
Analogamente, un server e un client basati su TCP saranno:
41
Server:
require "socket"
server = TCPServer.open(’localhost’, 12345)
while session = server.accept
msg = "Data corrente: #{Time.new.to_s}\n"
session.puts msg
session.close
end
Client:
require "socket"
client = TCPSocket.open(’localhost’, 12345)
while msg=client.gets
puts msg
end
client.close
Vediamo che, grazie alle proprieta del protocollo, server e client TCP sono piu semplici di quelliUDP.
Un piccolo server Web
I server web si basano sul protocollo TCP; vediamo ora come scrivere in pochissime riche di codiceun web-server minimale che ad ogni richiesta risponde con data e ora corrente; ovviamente larisposta dovra essere conforme alle specifiche del protocollo HTTP:
require ’socket’
server = TCPServer.new(’localhost’, 9090)
while (session = server.accept)
puts "Request: #{session.gets}"
session.print "HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"
session.print "<html><body><p>#{Time.new.to_s}</p></body></html>\r\n"
session.close
end
Proviamo ora a puntare il nostro browser a
http://localhost:9090/
42
Un server Web un po’ piu complesso
richiede la gemma mime-types
Questo server web ridotto all’osso server solo contenuti statici. Niente contenuti dinamici.
require ’rubygems’
require ’socket’
require ’mime/types’
class HttpServer
def initialize(session, request, basePath)
@session = session
@request = request
@basePath = basePath
end
def getFullPath()
fileName = nil
if @request =~ /GET .* HTTP.*/
fileName = @request.gsub(/GET /, ’’).gsub(/ HTTP.*/, ’’)
end
fileName = fileName.strip
unless fileName == nil
fileName = @basePath + fileName
fileName = File.expand_path(fileName, @defaultPath)
end
fileName << "/index.html" if File.directory?(fileName)
return fileName
end
def serve()
@fullPath = getFullPath()
src = nil
begin
if File.exist?(@fullPath) and File.file?(@fullPath)
if @fullPath.index(@basePath) == 0 #path should start with base path
contentType = getContentType(@fullPath)
@session.print "HTTP/1.1 200/OK\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n"
@session.print "Content-type: #{contentType}\r\n\r\n"
src = File.open(@fullPath, "rb")
while (not src.eof?)
buffer = src.read(256)
@session.write(buffer)
end
43
src.close
src = nil
else
# should have sent a 403 Forbidden access but
# then the attacker knows that such a file exists
@session.print "HTTP/1.1 404/Object Not Found\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n\r\n"
end
else
@session.print "HTTP/1.1 404/Object Not Found\r\n"
@session.print "Server: MySecondRubyWebServer 1.0\r\n\r\n"
end
ensure
src.close unless src == nil
@session.close
end
end
def getContentType(path)
types= MIME::Types.type_for(path)
if types.size == 0
return "text/html"
else
return types[0].content_type
end
end
end
basePath = "/home/stefano/Manoscritti/Imparando_Ruby/temp/"
server = TCPServer.new(’localhost’, 9090)
loop do
session = server.accept
request = session.gets
Thread.start(session, request) do |session, request|
HttpServer.new(session, request, basePath).serve()
end
end
Un server Web ancora piu complesso
Perche complicarsi la vita a scrivere ancora piu codice? I web server di prima erano a scopopuramente didattico, ma quando vogliamo qualcosa di piu perche non appoggiarsi a prodotti
44
stabili e testati?
Vediamo ora come usare webrick, un web server piuttosto complesso, nei nostri programmi ruby.In ogni caso questo listato e solo a titolo di esempio, affronteremo la programmazione web neldettaglio nei capitoli successivi.
richiede la gemma webrick
require ’webrick’
s = WEBrick::HTTPServer.new(
:Port => 2000,
:DocumentRoot => Dir.pwd + "/htdocs"
)
## mount sotto-directory
s.mount("/d1", WEBrick::HTTPServlet::FileHandler, "/srv/dati/dir1/public_html")
s.mount("/~stefano",
WEBrick::HTTPServlet::FileHandler, "/home/stefano/public_html",
true) #<= permette directory index
## mount di una "servlet"
class HelloServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
res.body = "<HTML>hello, world.</HTML>"
res[’Content-Type’] = "text/html"
end
end
s.mount("/hello", HelloServlet)
trap("INT"){ s.shutdown }
s.start
Rimando qui per ulteriori dettagli su come estendere webrick con delle “servlet”:
http://segment7.net/projects/ruby/WEBrick/servlets.htmlhttp://microjet.ath.cx/WebWiki/WEBrick.html
Volendo potremo anche usare Apache e mod ruby, ma lo vedremo meglio piu avanti...
45
Parte II
Programmazione web di base
46
Capitolo 5
CGI
5.1 Introduzione
**** i metodi http, e il passaggio di parametri ****
http://openskill.info/infobox.php?ID=357http://it.wikipedia.org/wiki/HTTP
5.2 CGI in ruby
L’approccio tradizionale alla programmazione Web e sicuramente costituito dai CGI. Un program-ma CGI sostanzialmente e un programma che viene eseguito dal webserver, e l’output viene inviatoal client. In ruby i CGI sono gestiti dalla libreria cgi che fornisce tutti gli strumenti necessari.
Cominciamo con un primo CGI, ciao.rb, che deve essere posizionato nella directory cgi-bin delnostro web server (e ovviamente deve essere reso eseguibile):
#!/usr/bin/env ruby
# un programma CGI deve sempre dire quale e il formato
# del contenuto che manda in output+una linea vuota
# contenuto html - tipo MIME text/html
puts "Content-type: text/html"
# linea vuota
puts ""
# stampa del codice html
puts "<html>"
puts "<head>"
puts "<title>ciao</title>"
47
puts "</head>"
puts "<body>"
puts "<h2>ciao mondo</h2>"
puts "</body>"
puts "</html>"
Andiamo quindi a
http://localhost/cgi-bin/ciao.rb
per vedere il risultato.
L’input di programmi cgi non e pero come il classico standard-input...i dati in ingresso vengono dati formattati dal browser in modo particolare; per questo per leggerlici viene in aiuto la libreria ’cgi’: (ciao2.rb)
#!/usr/bin/env ruby
require ’cgi’
cgi = CGI.new
#un aiuto al puts ce lo da’ sempre il modulo cgi
#invece di puts "Content-type: text/html"
puts cgi.header("type"=>"text/html")
# stampa del codice html
puts "<html>"
puts "<head>"
puts "<title>ciao</title>"
puts "</head>"
puts "<body>"
puts "<h2>ciao #{cgi[’nome’]}</h2>"
puts "</body>"
puts "</html>"
Andiamo quindi a
http://localhost/cgi-bin/ciao2.rb?nome=Stefano
per vedere il risultato. In questo caso nome e un parametro in ingresso con il metodo HTTP GET,ma anche se fosse stato inviato con POST la sua lettura sarebbe stata uguale.
5.3 CGI + ERB
Certo che pero e scomodo dover copiare/scrivere codice html direttamente immerso nel codiceruby...Un grande aiuto ce lo danno i sistemi di template, come ad esempio ERB (embedded-ruby)
48
proviamo cosı:
ciao erb.rb:
#!/usr/bin/env ruby
require ’erb’
require ’cgi’
cgi = CGI.new
puts cgi.header("type"=>"text/html")
input = File.read(’ciao_erb.erb’)
# carichiamo l’oggetto erb
eruby = ERB.new(input)
# qualche variabile tanto per provare
list = [’aaa’, ’bbb’, ’ccc’, ’ddd’]
name = ’Bla Bla Bla’
# Print the result of the two files.
puts eruby.result(binding())
e nella stessa cartella creiamo il file ciao erb.erb:
<html>
<head>
<title>Prova ERB</title>
</head>
<body>
Name: <%= name %>
<ul>
<% for item in list %>
<li><%= item %></li>
<% end %>
<%# questo e un commento %>
</ul>
</body>
</html>
Ecco, cosı e decisamente molto piu leggibile :-)
Come possiamo vedere, in un template erb il codice ruby e delimitato da <% e %>. Un po’ comePHP e ASP, insomma.
49
5.4 mod ruby
Si puo dire che ormai, in applicazioni web di una certa complessita, CGI e veramente pocoperformate, perche ad ogni richiesta il web server deve eseguire tutti il programma CGI.
Per questo ci vengono in aiuto alcuni componenti direttamente integrati nei web server, nel casodel popolare Apache parleremo di mod ruby.
E sufficiente installare mod ruby e inserire nella configurazione di apache
LoadModule ruby_module lib/httpd/modules/mod_ruby.so
<IfModule mod_ruby.c>
RubyRequire apache/ruby-run
<Location /ruby>
SetHandler ruby-object
RubyHandler Apache::RubyRun.instance
</Location>
<Files *.rbx>
SetHandler ruby-object
RubyHandler Apache::RubyRun.instance
</Files>
</IfModule>
Per far funzionare i nostri script ruby (che devono avere estensione .rbx) con performance deci-samente migliori.
50
Parte III
Ruby on Rails
51
**** DA FARE ****
52
Appendice A
Esercizi e soluzioni
A.1 Semplici operazioni con stringhe
A.1.1 Esercizio 1
Chiedere all’utente una stringa, verificare se e o meno palindroma.
#!/usr/bin/env ruby
puts "Inserisci una stringa, verifichero se e palindroma..."
# prendo una stringa dallo standard input, elimino il caporiga e
# la faccio diventare maiuscola (o minuscola)
# Il passo di farla diventare maiuscola/minuscola e fondamentale,
# altrimenti il programma non saprebbe riconoscere che la parola
# ’Anna’ e palindroma. (’Anna’ e diverso da ’annA’)
s1=gets.chomp.upcase
# ora creo una stringa invertendo i caratteri di quella di partenza
s2=s1.reverse
# e quindi faccio il confronto
if s1==s2 then
puts "La stringa e palindroma"
else
puts "La stringa NON e palindroma"
end
A.2 Metodi
A.2.1 Esercizio 1
Chiedere, per tre volte, due numeri all’utente, e per ogni richiesta dire se il primo numero edivisibile per il secondo o meno. (definire il metodo divisibile che accetta due parametri numerici
53
e ritorna true nel caso il primo numero sia divisibile per il secondo)
#!/usr/bin/env ruby
def divisibile(a,b)
return true if a%b==0
false
#scrittura compatta (al posto di tutte le righe precedenti):
#a%b==0
end
3.times do
puts "Inserisci a:"
a=gets.chomp.to_i
puts "Inserisci b:"
b=gets.chomp.to_i
if divisibile(a,b)
puts "#{a} e divisibile per #{b}"
else
puts "#{a} NON e divisibile per #{b}"
end
end
A.2.2 Esercizio 2
Scrivere il metodo is prime(x) che restituisce true se x e primo, false altrimenti. Testare poi ilmetodo chiedendo a un utente un numero, e verificandolo con la funzione.
#!/usr/bin/env ruby
def is_prime(x)
# 0 o negativi: non posso verificare se primo o meno, ritorno false...
return false if x < 1
# casi base: 1 e 2
return true if x==1 or x==2
# altri casi
pr=true
i=2
while i < x and pr
pr=false if x%i==0
i=i+1
end
pr
end
54
puts "Inserisci un numero, verifichero se e primo."
n=gets.chomp.to_i
if is_prime n
puts "#{n} e un numero primo"
else
puts "#{n} NON e un numero primo (oppure non posso verificare se e primo o meno)"
end
A.3 Operazioni con Array
A.3.1 Esercizio 1
Scrivere un programma che chiede all’utente quanti voti ha collezionato; chiedere i vari voti e poitrovare: massimo, minimo, media.
#!/usr/bin/env ruby
# inizializza l’array di voti
voti=[]
puts "Inserisci numero di voti:"
num=gets.chomp.to_i
# riempie l’array
num.times do |k|
n=k+1
puts "Inserisci voto numero #{n}:"
voti << gets.chomp.to_f
end
# calcola
somma=0.0
voti.each do |x|
somma=somma+x
end
media=somma/num
min=voti.min
max=voti.max
puts "Voto minimo: #{min}"
puts "Voto massimo: #{max}"
puts "Media: #{media}"
55
A.3.2 Esercizio 2
Dato un array cercare un elemento al suo interno senza usare i metodi di ricerca interni ad Array.Usare un metodo che restituisce l’indice della prima ricorrenza se l’elemento e presente, -1 in casocontrario.
#!/usr/bin/env ruby
def cerca_in_array(array, numero)
# r e il contatore della posizione in cui sono
r=0
array.each do |e|
# ritorno la posizione in caso l’elemento venga trovato
return r if e==numero
r=r+1
end
# ritorna -1 in caso non venga trovato
-1
end
# Inizializzo l’array
ar=[1, 2, 5, 6, 8, 12, 34, 56, 87]
ar_s=ar.join(",")
puts "Ecco l’array: #{ar_s}"
puts "Inserisci il numero da cercare:"
n=gets.chomp.to_i
posizione=cerca_in_array(ar, n)
if posizione==-1
puts "Numero non trovato."
else
puts "Il numero che cerchi e in posizione #{posizione}"
end
A.3.3 Esercizio 3
Con il metodo del crivello di eratostene trovare tutti i numeri primi da 1 a 100. Consiglio: vedereil crivello come un array in cui l’indice di un elemento corrisponde al numero, il valore in posizionek vale false se il numero e stato eliminato.
#!/usr/bin/env ruby
56
# numeri primi da 1 a 100 (Z)
Z=100
# secondo l’algoritmo trovo la radice quadrata del numero a cui arrivare
META=Math.sqrt(Z).to_i
# inizializzo l’array con tutti i valori a true
criv=[]
for i in (1..Z) do
criv[i]=true
end
# crivello vero e proprio
for i in (2..META) do
b=i+1
for k in (b..Z) do
criv[k]=false if k%i==0
end
end
puts "Ecco i numeri primi:"
for i in (1..Z) do
puts i if criv[i]
end
A.4 Operazioni con Hash
A.4.1 Esercizio 1
Dato un hash cercare la chiave relativa ad un suo elemento senza usare i metodi di ricerca interniad Hash. Usare un metodo che restituisce la chiave della prima ricorrenza se l’elemento e presente,nil in caso contrario.
#!/usr/bin/env ruby
def find_in_hash(hash, el)
# in questo caso tra || uso due variabili. Questo perche e definito cosı
# dal metodo .each_pair di hash. Vedere la documentazione ufficiale
hash.each_pair do |key, value|
return key if el==value
end
nil
end
h={}
57
h[’stefano’]=’nerd’
h[’mirko’]=’porseo’
puts "Ecco i valori contenuti nell’hash:"
# Vedere la documentazione ufficiale per capire il significato dei metodi
# seguenti
puts h.values.join(",")
puts "Che elemento devo cercare?"
el=gets.chomp
k=find_in_hash(h, el)
if k.nil?
puts "Elemento non trovato nell’hash."
else
puts "L’elemento cercato ha chiave #{k}"
end
A.5 Operazioni con File
A.5.1 Esercizio 1
Aprire un file di testo, e salvarlo come nome originale.ordinato dopo averne ordinato le righe.
#!/usr/bin/env ruby
puts "Inserisci il nome del file da ordinare:"
f1=gets.chomp
# verifica che il file sia leggibile, in caso contrario stampa messaggio
# di errore
if File.readable? f1 then
contenuto = File.read f1
# ora leggo le righe con uno split: (righe sara un array)
righe = contenuto.split("\n")
righe.sort!
File.open "#{f1}.sorted", ’w’ do |f|
righe.each do |row|
f.write "#{row}\n" # ricordiamoci il ritorno a capo!
end
end
puts "Fatto."
else
puts "Il file non esiste o non e leggibile"
end
58
A.6 Operazioni con Oggetti
A.6.1 Esercizio 1
Definire un oggetto di tipo Libro avente come attributi accessibili in sola lettura isbn, autore,titolo, editore, anno pubblicazione e prezzo. Tutti questi attributi devono essere settati infase di inizializzazione dell’oggetto.
class Libro
attr_reader :isbn, :autore, :titolo
attr_reader :editore, :anno_pubblicazione, :prezzo
# scritte due righe solo per semplicita di lettura da parte dell’utente.
# avrei potuto scrivere una riga solo
def initialize(isbn, autore, titolo, editore, anno_pubblicazione, prezzo)
@isbn = isbn
@autore = autore
@titolo = titolo
@editore = editore
@anno_pubblicazione = anno_pubblicazione
@prezzo = prezzo
end
end
A.6.2 Esercizio 2
Progettare la classe conto bancario (BankAccount), scrivere quindi un programma che costruiscaun conto bancario, versi in esso 1000 euro, prelevi da esso 500 euro, prelevi altri 400 euro e stampia video il saldo rimanente. (La progettazione della classe e libera)
class NegativeBalanceError < ArgumentError
end
class BankAccount
attr_reader :balance
def initialize
@balance = 0
end
def deposit (amount)
@balance = @balance + amount
end
59
def withdraw (amount)
if @balance - amount < 0
raise NegativeBalanceError
end
@balance = @balance - amount
end
end
ba=BankAccount.new
ba.deposit 1000
ba.withdraw 500
ba.withdraw 400
puts ba.balance
A.6.3 Esercizio 3
Progettare una classe Circle (cerchio), con i metodi area, per conoscere l’area, e perimeter, perconoscerne la circonferenza. Nel costruttore, indicare il raggio del cerchio. Usare π come costante.(PI=3.1415927)
class Circle
PI=3.1415927
def initialize(radius)
@radius = radius
end
def area
PI*(@radius**2)
end
def perimeter
2*PI*@radius
end
end
A.6.4 Esercizio 4
Scrivere un programma che chieda all’utente un numero intero e che poi stampi tutti i suoi fattori.Progettare e usare una classe FactorGenerator con i metodi next factor e has more factors?
.
60
class FactorGenerator
def initialize (n)
raise ArgumentError, ’n must be greater than zero’ if n<=1
@numero_da_scomporre = n
@attuale_scomposizione = n
end
def has_more_factors?
return false if @attuale_scomposizione==1
true
end
def next_factor
raise Exception, ’there are no more factors’ unless has_more_factors?
for i in (2..@attuale_scomposizione) do
if @attuale_scomposizione%i==0
@attuale_scomposizione = @attuale_scomposizione/i
# break serve per forzare l’uscita dal ciclo
break
end
end
i
end
end
numero = 150
f=FactorGenerator.new(numero)
while f.has_more_factors? do
puts f.next_factor
end
A.6.5 Esercizio 5
Aggiungere alla classe String il metodo num rows che conta le righe presenti nella stringa stessa.Consiglio: usare \n come separatore di linea.
class String
def num_rows
split("\n").size
end
end
61