Ruby language
   

Dati - i numeri                

Per iniziare a lavorare ci servono i dati da usare, logicamente. In questo senso Ruby è molto semplice proponendo classi potenti ed efficienti. Come detto tutto in Ruby è un oggetto e non fanno eccezione i dati di cui ci occupiamo in questo paragrafo.

Dobbiamo quindi come prima cosa occuparci dei dati vengono che vengono esposti tramite variabili, come in tutti i linguaggi che si rispettino. Una variabile in questo linguaggio deve avere un nome che inizia con un carattere US-ASCII compatibile e minuscolo. Più in generale è con quella tipologia di caratteri che deve essere scritto tutto un programma Ruby. La lunghezza minima di un identificatore di variabile è 1 mentre, per quanto ne so, non ci sono limiti alla lunghezza massima, ovviamente sta al buon senso non esagerare. Ogni identificatore può contenere lettere, numeri e il simbolo underscore _ , per restare tra quelli più tipicamente usati, e comunque la definizione di ogni singolo carattere deve essere di 8 bit. Una volta creata una variabile va valorizzata; in Ruby, quando ad una variabile vogliamo assegnare un valore, sia in fase di creazione della stessa o in un momento successivo qualunque, bisogna ricorrere all'apposito operatore che è il simbolo =, proprio come in C/C++, C# o Java.

x = 0
s1 = "ciao"
user_password = "1234"

_1 = 2

sono alcuni tra gli infiniti esempi possibili. L'interprete si farà sentire rumorosamente in caso di utilizzo di identificatori scorretti formalmente. Di norma comunque c'è abbastanza libertà, ovviamente non è possibile iniziare un nome di identificatore con un numero oppure con sequenze strane... capita raramente ma insomma cercate di evitare cose come:

2z = 8
!!! = "ciao"


Ruby permette anche definire degli identificatori con iniziale maiuscola: in questo caso si tratta di costanti. In Ruby questo concetto è un po' più rilassato rispetto ad altri linguaggi; una costante ha un suo valore ma la sua modifica non interrompe il programma, in realtà infatti dà origine solo ad un warning.

irb(main):001:0> Const = 9
=> 9
irb(main):002:0> Const = 10
(irb):2: warning: already initialized constant Const
(irb):1: warning: previous definition of Const was here
=> 10
irb(main):003:0> Const
=> 10


Come vedete il valore di Const è cambiato e l'interprete ci ha avvisati. A me questa cosa fa un po' sorcere il naso ma è così.

Fatta questa breve e doverosa premessa relativa ala presentazione dei dati andiamo ora ad esaminare i tipi a cui i dati devono appartenere.
Iniziamo dai numeri. Il nostro linguaggio prevede per la gestione dei valori numerici la seguente struttura:


Ogni elemento numerico è istanza di quella classe Numeric che sta alla radice (anche se è in alto) del piccolo albero disegnato. Da quella classe, che costituisce la base, il fondamento, discendono tutti i numeri che useremo nei nostri programmi in Ruby. Tutti gli interi a loro volta sono istanze di Integer e, direttamente, istanze di Fixnum o Bignum ha seconda che la loro definizione avvenga su 31 o più bit. In particolare Bignum accoglie numeri di grandezza arbitraria ed è importante notare che se il risultato di un'operazione è troppo grande per ricadere in Fixnum esso viene automaticamente e ed in modo trasparente riversato nell'ambito dei Bignum. Le altre classi sottostanti Numeric non hanno bisogno di troppe spiegazioni, il loro nome parla da se. Con questa semplice struttura Ruby gestisce gran parte delle necessità relative ai numeri. Solo Integer e Float sono elementi nativi in Ruby mentre Complex, BigDecimal e Rational fanno parte della libreria standard distribuita con il linguaggio. Non cambia nulla, ai fini pratici. Come sottolineato dall'autore in molti suoi scritti sul linguaggio, tutti gli oggetti numerici sono immutabili, ovvero non ci sono metodi che ne modifichino il valore.

A questo punto vediamo qualche semplice operazione di base iniziando a lavorare con i numeri interi:

  Esempio 2.1
1
2
3
4
5
6
7
8
x = 6
y = 5
puts(x + y)   # somma
puts(x - y)   # sottrazione
puts(x * y)   # moltiplicazione
puts(x / y)   # divisione
puts(x % y)   # resto della divisione
puts(x ** y)  # elevamento a potenza

L'output è prevedibile:

11
1
30
1
1
7776

Si può osservare che il risultato della divisione, alla riga 6, sia il quoziente e sia, ovviamente approssimato. Altri linguaggi, come Python, Cobra o Falcon, a fronte di tali operazioni restituiscono il risultato esatto, in questo caso 1,2 . A parer mio quest'ultima strada è preferibile, di solito calcoli del genere sono fatti per ottenere il risultato preciso, non per insegnare la differenza tra quoziente e resto :-). Ruby, come la maggior parte dei linguaggi la pensa diversamente. E' chiaro che è possibile senza problemi ottenere quanto vogliamo; una prima soluzione è avere i dati direttamente come float, se provate infatti a sostituire nella prime due righe entrambi i valori ma anche solo uno di essi col suo equivalente col formato in virgola, quindi 6.0 invece di 6 e 5.0 invece di 5, avrete il risultato richiesto. Ma se così non fosse e quindi aveste a disposizione i dati come interi potete effettuare una conversione al volo:

puts (Float(x) / y)

vi darà il valore corretto della divisione. Interessante notare che il simbolo % funziona anche con i float, non solo con gli interi. Ad esempio:

puts(1.7 % 0.3)

restituisce 0.2 come risultato.
Anche l'elevamento a potenza è molto potente e duttile. Dovrebbero bastare alcuni esempi,

  Esempio 2.2
1
2
3
4
5
6
7
8
puts(3 ** 2)
puts(3 ** -2)
puts(4 ** 2)
puts(4 ** 0.3333)
puts(10 ** -2.5)
puts(3 ** 3.0)
puts(19 ** 19)
puts(2 ** 2 ** 3)

Ecco l'output:

9
1/9
16
1.5873277001587598
0.0031622776601683794
27.0
1978419655660313589123979
256


L'ultima riga ci mostra che l'espressione 2 ** 2 ** 3 viene valutata come 2 ** 8  ovvero 2 ** (2 ** 3) non 4 ** 3 cioè (2 ** 2) ** 3. Da tenere presente.

Comunque fatevi i vostri esempi. Come è evidente ci si può fare di tutto, naturalmente avrete notato il differente comportamento quando si opera con gli interi piuttosto che con i float. Vale la pena ricordare che il passaggio da Fixnum a Bignum, ad esempio osservate la riga 7, è automatico e permette di utilizzare numeri di grandezza arbitraria. Al contrario i numeri in virgola non possono superare il valore costante Float::MAX che è peraltro decisamente capiente. Per inciso annotiamo che i numeri interi possono anche scritti all'interno dei vostri programmi con il carattere underscore _ che funge da separatore, Ad esempio
1000000 lo possiamo scrivere 1_000_000 (il carattere _ non può ovviamente andare nè all'inizio nè alla fine del numero); questa feature non sarà fondamentale ma può essere utile per la più agevole lettura di un programma.
Come altri linguaggi anche Ruby è in grado di gestire valori binari, ottali ed esadecimali. La cosa è gestita semplicemente tramite prefissi:

0b oppure 0B per i binari
0 iniziale per gli ottali
0x o 0X per gli esadecimali

Usando la shell di Ruby (irb) presento questo semplice esempio:

irb(main):001:0> x = 0b10
=> 2
irb(main):002:0> y = 07
=> 7
irb(main):003:0> z = 0xA
=> 10
irb(main):004:0> r = x + y + z
=> 19


L'esempio qui sopra somma un numero binario, un ottale ed un esadecimale. Incontreremo ancora gli interi ed alcune loro proprietà. Ricordatevi sempre che sono degli oggetti anche loro e pertanto sono molto più "attivi" di quanto si creda siano i numeri puri e semplici.

I floating point si presentano in modo del tutto simile a quanto si trova in altri linguaggi. Come unica accortezza bisogna considerare che Ruby, per questioni sintattiche, richiede una cifra sia prima sia dopo il punto che rappresenta l'ingresso alla parte decimale. Ovvero:

0.1 va bene
.1 non va bene.

Quella attraverso il punto è la rappresentazione più tipica:

1.23
24.567
1_000_000.03

esiste anche quella esponenziale:

5.62557e3 equivale a:
5625.57

è permesso anche usare la E maiuscola.

Le operazioni sui float non differiscono da quelle viste sugli interi e qualosa abbiamo già visto nell'esempio 2.2

  Esempio 2.3
1
2
3
4
5
6
7
8
n1 = 7.0
n2 = 3.3
puts n1 + n2
puts n1 - n2
puts n1 * n2
puts n1 / n2
puts n1 % n2
puts n1 ** n2

che da come output:

10.3
3.7
23.099999999999998
2.121212121212121
0.40000000000000036
614.9269571447019

A proposito dell'elevamento a potenza osserviamo che per elevare ad esempio x ad 1/3 è sbagliato scrivere:

x ** (1/3) perchè 1/3 fa 0 ed il risultato è 1, bensì:
x ** (1/3.0) e tutto funziona.

Le parentesi tonde sono necessarie altrimenti l'interprete non comprenderebbe correttamente le nostre intenzioni; ad esempio se la prima riga fosse scritta
x** 1/3 allora verrebbe interpretata come (x ** 1) / 3.
Proseguiamo ora con qualche osservazione di carattere generale.

0/0
genera un errore di tipo: ZeroDivisionError: divided by 0
0.0 / 0.0
restituisce NaN ovvero not a numer
4 / 0
genera l'errore ZeroDivisionError visto prima ma
4 / 0.0
ci dà come risultato:
Infinity

I valori NaN e Infinity non possono essere usati all'interno di altre operazioni.

Nel suo eccellente testo sul linguaggio l'autore di Ruby in persona ci mostra alcune differenze rispetto ad esempio al C/C++

-7/3

ha come risultato -2.3333333 ma la divisione, restituendo solo la parte intera, deve arrotondare, da una parte o dall'altra. Java, Kotlin, C++, Go ecc restituiscono -2 il nostro Ruby ci fa apparire un bel -3 in quanto arrotonda verso l'infinito negativo (nb: la cosa mi lascia perplesso). Ancora:

-7%3

restiuisce -1 in Java o C mentre ci dà 2 in Ruby. Questo perchè in Ruby il risultato di questa operazione ha sempre il segno del secondo operando, non del primo. Se questo passaggio non è chiaro potete comunque ragionare come segue (nb: anche qui sono perplesso) Quindi:
-7%3 finisce per essere calcolato come -7 - (-7/3 * 3) = -7 - (-3 * 3) = -7 + 9 = 2. Se proviamo invece
-14 % 5 sarà 14 - (-14 / 5 * 5) = 14 - ( -3 * 5) = -14 + 15 = 1 oppure
-28 % 6 ci darà 2 indovinate un po' perchè. Facciamoce una ragione e teniamo presente la cosa.

Per aumentare la confusione Ruby ha ancheil metodo remainder che si comporta relativamente al problema del resto come il C, Java ecc...


-28.remainder 6
ci dà -4 come risultato.


non so quanto questo vi possa piacere o confondere ma le cose stanno così.

Uno dei primi problemini con cui ci si scontra è quello del ricavare i numeri stessi, ad esempio tramite inputazione dallo standard input o tramite lettura da un file, da una penna ottica o insomma da dove volete. Normalmente l'input è in formato stringa per cui occorre effettuare una conversione. Ruby mette a disposizione vari strumenti per questo task e in particolare vengono comodi i metodi to_i e to_f per la conversione rispettivamente verso gli interi e verso i float:

"1234".to_i ci restituisce il numero 1234
"1.234".to_f invece restituisce il numero 1.234 così come
"1.23345e3".to_f ci dà 1233.45

Questi metodi sono abbanza duttili da poter performare il loro lavoro in modo abbastanza duttile; ad esempio se una stringa inizia in forma numerica ma è seguita da una parte che non lo è, il metodo è in grado di arrestarsi estraendo l'informazione utile:

irb(main):006:0> "100 euro".to_i + 1
=> 101


Questa caratteristica è utile per la gestione dell'input da tastiera in quanto immettere ad esempio il numero 123 significa inserire la stringa "123\n" per cui è avere un metodo che toglie di mezzo le parti non interessanti ai fini dell'ottenimento del numero immesso. Analogo comportamento hanno altri metodi, ovvero hex e oct tramite i quali è possibile ottenere la conversione rispettivamente in esadecimale e in ottale, come intuitivo.

irb(main):001:0> "200".to_i
=> 200
irb(main):002:0> "200".oct
=> 128
irb(main):003:0> "200".hex
=> 512

Vediamo ora un semplice programma che riassume un po' di queste cose:

  Esempio 2.4
1
2
3
4
5
6
print("Inserisci un numero: ")
n1 = gets()
print("Inserisci un altro numero: ")
n2 = gets()
n3 = n1.to_i + n2.to_i
print("La somma dei due vale: ", n3)

Niente di difficile.

Diverso invece è il classico problema relativo al confronto tra due numeri floating point. Il problema nasce evidentemente solo facendo un semplice calcolo:

irb(main):003:0* 2.2 + 0.1
=> 2.3000000000000003

che porta all'immediata conseguenza:

irb(main):006:0> 2.2 + 0.1 == 2.3
=> false

La prima soluzione suggerita, la più semplice, è quella di usare i BigDecimal. Con questi tutto funziona ma il loro uso, specie se intensivo non è privo di controindicazioni:  sono grassi e grossi, estremamenti pesanti in termini prestazionali. Usarli, conoscendo tra l'altro le caratteristiche velocisitiche non da primato di Ruby, deve essere fatto con buon senso. I BigDecimal non possono sostituire i float in e per tutto. Non facendo parte del core del linguaggio ma della libreria standard, devono essere richiamati per mezzo della clausola require:

irb(main):001:0> require 'bigdecimal'
=> true
irb(main):002:0> x = BigDecimal("2.2")
=> #<BigDecimal:32cc100,'0.22E1',18(18)>
irb(main):004:0> x + 0.1 == 2.3

La libreria va richiamata ponendola all'interno di una coppia di singoli apici. L'innesco per poter utilizzare questa tipologia di numeri, come è evidente, è costituito da una stringa come input. Si tratta chiaramente di qualcosa di particolare quindi vale la pena di parlarne un po' prima di andare oltre. Un elemento definito tramite BigDecimal è in realtà espresso internamente in formato scientifico ciò che favorisce la sua potenza espressiva (ed il suo peso...). Per approfondire la cosa possiamo usare il metodo split permette di vederne la reale conformazione:

irb(main):003:0> BigDecimal("123456").split
=> [1, "123456", 10,
6]

ovvero: 1 * 0.123456 * 10**6 nel caso di numeri negativi avremmo -1 davanti. La parte decimale vera e propria è arbitrariamente grande così come l'esponente. Ecco la spiegata la grande forza espressiva di BigDecimal. Di essi avremo modo di riparlare più avanti così come faremo per i Rational ed i Complex.

Pertanto, usando BigDecimal, l'eguaglianza è assicurata ma come detto questa "sicurezza" ha un costo che può risultare salato. Dunque, come si può risolvere in via alternativa la questione? Da un punto di vista teorico esattamente come in altri linguaggi. Attraverso una differenza con una quantità piccola a piacere. Da un punto di vista pratico abbiamo la possibilità di estendere la classe Float con un nostro metodo di confronto (si, come vedremo Ruby permette di estendere facilmente le classi esistenti). Su Internet troverete molti esempi relativi a questo tipo di soluzione. Comunque in modo approssimato, potete anche fare una cosa molto semplice come la seguente:

irb(main):019:0> ((2.2 + 0.1) - 2.3).abs < 0.00000000001
=> true

per avere la ragionevole certezza che la distanza tra i due dati, ammesso che ve ne sia, non costituirà un problema.

Metodi - interi

In Ruby tutto è un oggetto quindi tutto è dotato di metodi atti alla sua manipolazione. I numeri interi ne hanno parecchi e vediamo in questa sede quelli più interessanti:

abs restituisce il valore assoluto del numero: -3.abs vale 3.
chr restituisce il carattere relativo al numero indicato seguendo la tabella consueta

irb(main):008:0> puts(48.chr)
0
downto effettua una iterazione dal valore corrente a quello indicato come argomento del downto.

irb(main):011:0> 5.downto(1) {puts("ciao")}
ciao
ciao
ciao
ciao
ciao
even? Restituisce true se il numero è pari false se è dispari

irb(main):012:0> puts(2.even?)
true
=> nil
irb(main):013:0> puts(3.even?)
false
gcd restituisce il massimo comun divisore tra due numeri uno dei quali è l'argomento

irb(main):016:0> puts (328.gcd(176))
8
lcm restituisce il minimo comune multiplo tra due numeri

irb(main):017:0> puts (328.lcm(176))
7216
next ci dà l'intero successivo

2.next vale 3
odd? Identico a even? visto in precedenza riferito però alla disparità del numero.
ord restituisce l'intero stesso o il numero corrispondente al carattere nella consueta codifica.

irb(main):023:0> puts('m'.ord)
109
irb(main):024:0> puts(1200.ord)
1200
pred simile a next solo che restituisce il numero precedente all'intero indicato invece del successivo.
round Arrotonda l'intero in formato float su una data precisione. La precisione può essere anche negativa. Sugli interi mi pare di ridotta utilità
succ identico a next
times effettua un'iterazione da 0 a n-1

irb(main):058:0> 5.times do |x| print x, " " end
0 1 2 3 4 => 5
to_i, to_int, to_r I primi due sono inutili sugli interi, il terzo lavorando sugli interi è banale:

irb(main):059:0> puts(3.to_r)
3/1
upto Effettua una iterazione da n al limite indicato

irb(main):060:0> 5.upto(10) do |x| print x, " " end
5 6 7 8 9 10 => 5

Metodi - float

Anche i float hanno una ricca dotazione di metodi ed anche in questo caso vediamo i più interessanti:

abs restituisce il valore assoluto del float.
angle, arg restituisce 0 se il valore è positivo pigreco se negativo

irb(main):063:0* puts (3.4.angle)
0
irb(main):064:0> puts (-3.4.angle)
3.141592653589793
ceil Restituisce il più piccolo intero maggiore o uguale al float dato

irb(main):065:0> puts (3.4.ceil)
4
=> nil
irb(main):066:0> puts (-3.4.ceil)
-3
coerce restituisce un array composto dal numero chiamante e dall'argomento
ovvero 3.4.coerce(5) restituisce un array composto dai numeri 3.4 e 5
eql? restituisce true solo se l'oggetto argomento eè un float con lo stesso valore del float chiamante.

irb(main):068:0> puts(2.2.eql?(2.2))
true
floor simile a ceil solo che restituisce l'intero più grande minore o uguale al float chiamante
infnite? Interessante test che restituisce nil se il valore è finito, -1 se è -∞ e + 1 se ∞

irb(main):072:0> puts ((-1.0/0.0).infinite?)
-1
modulo restituisce il resto di una divisione

irb(main):073:0> puts(3.3.modulo(2))
1.2999999999999998
phase uguale ad angle e arg
rationalize Nel caso dei float è più interessante (si può applicare anche agli interi ma ha poco senso) in quanto restituisce il razionale corrispondente al float indicato.

irb(main):075:0> puts (3.4.rationalize())
17/5
round arrotonda il chiamante ad una precisione stabilita.

irb(main):076:0> puts (1.23456.round(3))
1.235
=> nil
irb(main):077:0> puts (1.23456.round(1))
1.2
=> nil
irb(main):078:0> puts (1.23456.round(5))
1.23456
=> nil
irb(main):079:0> puts (1.23456.round(-2))
0
=> nil
irb(main):080:0> puts (1.23456.round(-0))
1
=> nil
to_i
to_r
to_s
trasforma il numero in intero, in razionale (come rationalize) e in stringa
truncate restituisce il float troncato a intero

irb(main):081:0> puts (3.9.truncate)
3