|
|||
Precedente < | Indice ^ |
Prossimo
> |
Finora ci siamo divertiti ad implementare pezzi di codice del nostro jukebox, ma siamo stati negligenti. Abbiamo osservato gli array, gli hash ed i processi, ma non abbiamo realmente affrontato gli altri tipi basilari di Ruby: i numeri, le stringhe,i range e le espressioni regolari. Utilizziamo ora alcune pagine su questi costrutti di base.
Ruby supporta i numeri interi e a virgola mobile. Gli interi possono avere
qualsiasi dimensione (fino ad un massimo determinato dall' ammontare della
memoria libera nel vostro sistema). Gli interi all' interno di un certo
valore (normalmente -230 fino a 230-1 oppure
-262 fino a 262-1) sono mantenuti internamente in forma
binaria, e sono oggetti della classe Fixnum
. Gli interi al di
fuori di questi valori sono salvati in oggetti della classe
Bignum
(correntemente implementati come configurazione di una
lunghezza-variabile di un piccolo intero). Questo processo è trasparente, e
Ruby gestisce automaticamente la conversione avanti e indietro.
num = 8 7.times do print num.type, " ", num, "\n" num *= num end |
produce:
Fixnum 8 Fixnum 64 Fixnum 4096 Fixnum 16777216 Bignum 281474976710656 Bignum 79228162514264337593543950336 Bignum 6277101735386680763835789423207666416102355444464034512896 |
Potrete scrivere i numeri interi usando un segno opzionale al primo posto,
un indicatore opzionale di base (0
per gli ottali,
0x
per gli esadecimali, oppure 0b
per i binari),
seguiti da una stringa di cifre nella corretta base di appartenenza. I
caratteri di underscore sono ignorati nella stringa di cifre.
123456 # Fixnum 123_456 # Fixnum (underscore ignorato) -543 # Fixnum Negativo 123_456_789_123_345_789 # Bignum 0xaabb # eaadecimale 0377 # Ottale -0b101_010 # Binario (negativo) |
Si possono anche prendere il valore intero di un corrispondente carattere
ASCII o di una sequenza di escape semplicemente precedendolo con un punto
interrogativo. Le combinazioni di controllo ed i metacaratteri possono anche
essere generati utilizzando ?\C-x, ?\M-x, e
?\M-\C-x. La versione di controllo di un valore è la stessa di
``value & 0x9f
''. La versione meta di un valore è
``value | 0x80
''. Alla fine la sequenza ?\C-? genera
un valore ASCII di delete, 0177
.
?a # codice carattere ?\n # codice di nuova linea(0x0a) ?\C-a # control a = ?A & 0x9f = 0x01 ?\M-a # meta pone il bit a 7 ?\M-\C-a # meta e control a ?\C-? # cancella carattere |
Un numero letterale con un punto decimale e/o un esponente è trasformato
in in un oggetto Float
, corrispondente al tipo di dati
double
dell' architettura nativa. Si deve seguire il punto
decimale con una cifra, come 1.e3
tenta di invocare il metodo
e3
nella classe Fixnum
.
Tutti i numeri sono oggetti, e rispondono ad una varietà di messaggi
(elencati completamente alle pagine 294, 318, 319, 328, e 354). Così,
differentemente ( dico ) dal C++, trovate il valore assoluto di un numero
semplicemente scrivendo aNumber.abs
, e non
abs(aNumber)
.
Gli interi supportano anche numerosi utili iteratori. Ne abbiamo già visto
uno---7.times
nel codice d' esempio a pagina 49. Altri includono
upto
e downto
, per interagire su e giù tra due
interi, e step
, che assomiglia ad un tradizionale ciclo
for
.
3.times { print "X " } 1.upto(5) { |i| print i, " " } 99.downto(95) { |i| print i, " " } 50.step(80, 5) { |i| print i, " " } |
produce:
X X X 1 2 3 4 5 99 98 97 96 95 50 55 60 65 70 75 80 |
Finalmente, un avviso per gli utenti del Perl. Le stringhe che contengono numeri non sono automaticamente convertite in numeri quando usate in espressioni. Questo tende ad essere ingannati quando si leggono numeri da un file. Il codice seguente (probabilmente) non fà quello che si vorrebbe.
DATA.each do |line| vals = line.split # split line, storing tokens in val print vals[0] + vals[1], " " end |
Dategli in pasto un file che contiene
3 4 5 6 7 8 |
e riceverete come risultato ``34 56 78.'' Che cosa è accaduto?
Il problema stà nel fatto che l' input è stato letto come una stringa, e
non come un numero. L' operatore di somma concatena le stringhe, questo è il
motivo per cui abbiamo quel risultato. Per risolverlo, usiamo il metodo String#to_i
per
convertire la stringa in un intero.
DATA.each do |line| vals = line.split print vals[0].to_i + vals[1].to_i, " " end |
produce:
7 11 15 |
Le stringhe di Ruby sono semplicemente una sequenza di byte ad 8-bit.
Normalmente mantengono i caratteri stampabili, ma non è una necessità. Una
stringa può anche mantenere i dati in forma binaria. Le stringhe sono oggetti
della classe String
.
Le stringhe sono spesso create utilizzando valori letterali--- sequenze di caratteri tra delimitatori. Poichè i dati binari sono evidentemente difficoltosi da rappresentare all'interno del sorgente del programma, si possono posizionare varie sequenze di escape in una stringa letterale. Ognuno è sostituito con il corrispondente valore binario non appena il programma è compilato. I tipi di delimitatore di stringhe determinano il grado di esecuzione della sostituzione. All' interno delle stringhe con un solo apice, due consecutivi backslashe ( \\ ) sono sostituiti da uno solo, e un backslash seguito da un apice rimane l' apice stesso.
'escape using "\\"' |
» | escape using "\" |
'That\'s right' |
» | That's right |
Le stringhe con i doppi apici support a boatload more escape sequences. Il
più comune è probabilmente ``\n'', il carattere di nuova riga. La tavola 18.2
a pagina 205 fornisce la lista completa. Ulteriormente, si può sostituire il
valore di un espressione di Ruby in una stringa utilizzando la sequenza
#{
expr }
. Se l' espressione è
una variabile globale, una variabile di classe, oppure una variabile istanza,
si possono omettere le graffe.
"Seconds/day: #{24*60*60}" |
» | Seconds/day: 86400 |
"#{'Ho! '*3}Merry Christmas" |
» | Ho! Ho! Ho! Merry Christmas |
"This is line #$." |
» | This is line 3 |
Esistono tre ulteriori modi per costruire stringhe letterali:
%q
, %Q
, e ``here documents.''
%q
e %Q
iniziano stringhe con uno o due
apici.
%q/general single-quoted string/ |
» | general single-quoted string |
%Q!general double-quoted string! |
» | general double-quoted string |
%Q{Seconds/day: #{24*60*60}} |
» | Seconds/day: 86400 |
Il carattere che segue la ``q'' o la ``Q'' è il delimitatore. Se esiste una parentesi quadra, graffa, tonda, o il segno di meno aperti la stringa verrà letta fino al segno di chiusura. Diversamente la stringa verrà letta fino alla successiva occorrenza con lo stesso delimitatore.
Finalmente, si può costruire una stringa utilizzando il costrutto here document.
aString = <<END_OF_STRING The body of the string is the input lines up to one ending with the same text that followed the '<<' END_OF_STRING |
Un ``here document'' consiste in un insieme di righe fino, ma non incluse,
alla fine della stringa che specificate dopo il carattere
<<
. Normalmente, il carattere di chiusura deve iniziare
nella prima colonna. Comunque, se inserite un segno di meno dopo il carattere
<<
, potrete fare andare a capo il carattere di
chiusura.
print <<-STRING1, <<-STRING2 Concat STRING1 enate STRING2 |
produce:
Concat enate |
String
è probabilmente la classe più grande precostituita in
Ruby, con oltre 75 metodi standard. La nostra intenzione non è quella di
approfondirli tutti in questo momento; la biblioteca di consultazione dispone
di una lista completa. Invece, osserveremo alcune stringhe di espressioni
comuni---cose che sono facilmente implementabili durante la programmazione
quotidiana.
Ritorniamo al nostro jukebox. Sebbene sia stato disegnato per la connessione ad internet, mantiene localmente in un hard disk alcune copie delle canzoni. In questo modo, se la connessione dovesse venir meno saremo sempre in grado di intrattenere i clienti.
Per ragioni di storico (non ne esistono di altre?), la lista delle canzoni è mantenuta come una serie di righe in un file inattivo. Ogni riga conserva il nome del file che contiene la canzone, la durata, l' artista ed il titolo, il tutto in campi separati da una barra verticale. Un esempio di file potrebbe incominciare così:
/jazz/j00132.mp3 | 3:45 | Fats Waller | Ain't Misbehavin' /jazz/j00319.mp3 | 2:58 | Louis Armstrong | Wonderful World /bgrass/bg0732.mp3| 4:09 | Strength in Numbers | Texas Red : : : : |
Osservando i dati, è chiaro che useremo alcune classi dei numerosi metodi
di String
per estrarre e pulire i campi prima di creare gli
oggetto Song
basati su di essi. Come minimo avremo bisogno
di:
In nostro primo lavoro sarà di scindere ogni riga nei campi: String#split
svolgerà
il compito felicemente. In questo caso, passeremo a split
un'
espressione regolare, /\s*\|\s*/
, la quale seziona le linee in
segni ovunque split
trova una barra verticale, opzionalmente
circondati da spazi. E, poichè la linea letta dal file si trascina una nuova
riga, useremo String#chomp
per
toglierla prima di iniziare la divisione della stessa..
songs = SongList.new songFile.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) songs.append Song.new(title, name, length) end puts songs[1] |
produce:
Song: Wonderful World--Louis Armstrong (2:58) |
Sfortunatamente, chiunque abbia creato il file originale ha inserito i
nomi degli artisti in colonne, alcune di esse contengono degli spazi
ulteriori. Ciò si presenta male sul nostro pannello supermoderno, così
sarebbe meglio rimuovere questi spazi prima di andare troppo lontano.
Esistono molti modi per realizzarlo, ma probabilmente il più semplice è String#squeeze
, che
regola il corso dei caratteri ripetuti. Useremo la formula
squeeze!
del metodo, che modifica la stringa adatta.
songs = SongList.new songFile.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(" ") songs.append Song.new(title, name, length) end puts songs[1] |
produce:
Song: Wonderful World--Louis Armstrong (2:58) |
Alla fine affrontiamo il problema minore ,il formato del tempo: il file
segna 2:58 e noi vogliamo il valore in secondi, 178. Possiamo usare
nuovamente split
, questa volta dividendo il campo time attorno
al carattere dei due punti.
mins, secs = length.split(/:/) |
Invece, useremo un metodo connesso. String#scan
è simile a
split
in quanto interrompe una stringa in grosse sezioni in base
al modello. Comunque, sfortunatamente per split
, con
scan
specificate il modello di stringa che volete che venga
trovato. Nel nostro caso vogliamo cercare uno o più numeri per entrambi i
componenti dei minuti e dei secondi. Il modello per una o più cifre è
/\d+/
.
songs = SongList.new songFile.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(" ") mins, secs = length.scan(/\d+/) songs.append Song.new(title, name, mins.to_i*60+secs.to_i) end puts songs[1] |
produce:
Song: Wonderful World--Louis Armstrong (178) |
Il nostro jukebox ha la capacità di ricerca. Fornita una parola per un
titolo di una canzone o per il nome di un artista, elencherà tutte le
corrispondenze. Digitando ``fats,'' potrebbe restituire canzoni tipo Fats
Domino, Fats Navarro, e Fats Waller, per esempio. Implementeremo la cosa
creando una classe indice. Fornendogli un oggetto ed alcune stringhe, creerà
l' indice dell' oggetto sotto ogni parola ( di due o più caratteri) che
risultano in quelle stringhe. Questo illustrerà un poco meglio i molti metodi
della classe String
.
class WordIndex def initialize @index = Hash.new(nil) end def index(anObject, *phrases) phrases.each do |aPhrase| aPhrase.scan /\w[-\w']+/ do |aWord| # estrae ogni parola aWord.downcase! @index[aWord] = [] if @index[aWord].nil? @index[aWord].push(anObject) end end end def lookup(aWord) @index[aWord.downcase] end end |
Il metodo String#scan
estrae gli
elementi da una stringa che corrispondono ad un' espressione regolare. In
questo caso, il modello ``\w[-\w']+
'' confronta tutti i
caratteri che appaiono in una parola, seguiti da una o più cose specificate
tra le parentesi quadre (un trattino, un altro carattere della parola, oppure
un apice). Parliamo più approfonditamente delle espressioni regolari a
partire da pagina 58. Per rendere le nostre ricerche indipendenti dal 'case
sensitive', segnamo entrambe le parole da estrarre e le parole usate come
chiavi durante la ricerca del valore minuscolo. Notate il punto esclamativo
alla fine del nome del primo metodo downcase!
. Come con il
metodo squeeze!
utilizzato precedentemente, questo è un
indicatore che il metodo modificherà il destinatario dove si trova, nel
nostro caso convertendo la stringa in formato minuscolo[Esiste un bug di
lieve entità nell' esempio: la canzone ``Gone, Gone, Gone'' sarebbe
indicizzata tre volte. Potete sistemarlo?]
Estenderemo la nostra classe SongList
per indicizzare le
canzoni così come sono state aggiunte, ed un metodo per cercare una canzone
data la parola.
class SongList def initialize @songs = Array.new @index = WordIndex.new end def append(aSong) @songs.push(aSong) @index.index(aSong, aSong.name, aSong.artist) self end def lookup(aWord) @index.lookup(aWord) end end |
Finalmente, lo testeremo nella sua integrità.
songs = SongList.new songFile.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze!(" ") mins, secs = length.scan(/\d+/) songs.append Song.new(title, name, mins.to_i*60+secs.to_i) end puts songs.lookup("Fats") puts songs.lookup("ain't") puts songs.lookup("RED") puts songs.lookup("WoRlD") |
produce:
Song: Ain't Misbehavin'--Fats Waller (225) Song: Ain't Misbehavin'--Fats Waller (225) Song: Texas Red--Strength in Numbers (249) Song: Wonderful World--Louis Armstrong (178) |
Potremo passare le prossime 50 pagine ad osservare tutti i metodi nella
classe String
. Comunque, andremo oltre per approfondire un
datatype più semplice: i range.
I range sono ovunque: da Gennaio a Dicembre, da 0 a 9, da superlativo a ben fatto, le linee da 50 fino alla 67, e così via. Se Ruby ci aiuta con la realtà dei modelli, sembrerebbe naturale che supporti questi range. Infatti Ruby fà ancora meglio: usa i range per implementare tre differenti caratteristiche: le sequenze, le condizioni e gli intervalli.
Il primo, e forse il più naturale uso dei range, è l' espressione di una sequenza. Le sequenze hanno un punto di partenza, un punto di arrivo, ed un modo di produrre una sequenza di valori successivi. In Ruby, queste sequenze sono create utilizzando gli operatori range ``..'' e ``...''. La forma con i due punti crea un range inclusivo, mentre quella con tre punti ne crea uno con i due estremi esclusi.
1..10 'a'..'z' 0...anArray.length |
In Ruby, sfortunatamente come nelle prime versioni del Perl, i range non
sono rappresentati internamente come una lista: la sequenza 1..100000 è
contenuta in un oggetto Range
, che contiene i riferimenti a due
oggetti Fixnum
. Nel momento del bisogno, si potranno convertire
il range in una lista utilizzando il metodo to_a
.
(1..10).to_a |
» | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
('bar'..'bat').to_a |
» | ["bar", "bas", "bat"] |
I range implementano i metodi che vi permettono di iterarli e di testare i loro contenuti in una varietà di modi.
digits = 0..9 |
||
digits.include?(5) |
» | true |
digits.min |
» | 0 |
digits.max |
» | 9 |
digits.reject {|i| i < 5 } |
» | [5, 6, 7, 8, 9] |
digits.each do |digit| |
||
dial(digit) |
||
end |
Fin' ora vi abbiamo mostrato i range di numeri e stringhe. Comunque, come
vi aspettereste da un linguaggio object-oriented, Ruby è in grado di creare
range basandosi sugli oggetti che voi definite. L' unica restrizione è che
gli oggetti devono reagire a succ
restituendo il prossimo
oggetto nella sequenza e che l' oggetto deve essere comparabile utilizzando
<=>
, l' operatore di comparazione generale. Talvolta
denominato operatore di spazio, <=>
compara due valori,
restituendo -1, 0, oppure +1 a seconda se il primo valore sia minore, uguale
o maggiore del secondo.
Questa è una semplice classe che rappresenta le righe del segno ``#''. Potremo utilizzarlo come troncone testuale, quando testeremo il controllo del volume del jukebox.
class VU include Comparable attr :volume def initialize(volume) # 0..9 @volume = volume end def inspect '#' * @volume end # Support for ranges def <=>(other) self.volume <=> other.volume end def succ raise(IndexError, "Volume too big") if @volume >= 9 VU.new(@volume.succ) end end |
Lo testeremo creando una classe di oggetti VU
.
medium = VU.new(4)..VU.new(7) |
||
medium.to_a |
» | [####, #####, ######, #######] |
medium.include?(VU.new(3)) |
» | false |
Per rappresentare al meglio le sequenze, i range possono anche essere usati come espressioni condizionali. Per esempio, il seguente frammento di codice stampa una serie di linee dallo standard input, dove la prima riga di ogni serie contiene la parola ``start'' e l' ultima riga la parola ``end.''
while gets print if /start/../end/ end |
Dietro le quinte, il range tiene traccia dello stato di ognuno dei testi. Mostreremo alcuni esempi di ciò nella descrizione dei loop che inizia a pagina 84.
L' uso finale della versatilità dei range è come test di intervallo:
verificando se alcuni valori cadono all'interno dell' intervallo
rappresentato dal range. Ciò è fatto utilizzando ===
, il caso
dell' operatore di uguaglianza.
(1..10) === 5 |
» | true |
(1..10) === 15 |
» | false |
(1..10) === 3.14159 |
» | true |
('a'..'j') === 'c' |
» | true |
('a'..'j') === 'z' |
» | false |
L' esempio di un espressione di un caso a pagina 83 mostra questo test in azione, mentre determina lo stile del jazz in base all'anno.
Indietro a pagina 53, quando stavamo creando una lista di canzoni da un
file, abbiamo utilizzato un' espressione regolare per determinare il
delimitatore del campo all'interno del file di input. Asserivamo che l'
espressione line.split(/\s*\|\s*/)
selezionava una barra
verticale circondata da opzionali spazi vuoti. Approfondiamo le espressioni
regolari per vedere perchè quella asserzione sia vera.
Le espressioni regolari sono utilizzate per selezionare dei modelli all'interno delle stringhe. Ruby fornisce un supporto che esegue la selezione del modello ed una sostituzione comoda e breve. In questa sezione lavoreremo attraverso tutte le principali caratteristiche delle espressioni regolari. Ci saranno dettagli di cui non ci occuperemo: osservate pagina 207 per maggiori informazioni.
Le espressioni regolari sono oggetti del tipo Regexp
. Possono
essere create chiamando il costruttore esplicitamente oppure utilizzando le
forme letterali /pattern/ e %r\pattern\.
a = Regexp.new('^\s*[a-z]') |
» | /^\s*[a-z]/ |
b = /^\s*[a-z]/ |
» | /^\s*[a-z]/ |
c = %r{^\s*[a-z]} |
» | /^\s*[a-z]/ |
Una volta che disponete di un oggetto di un espressione regolare, potrete
selezionarlo all'interno di una stringa utilizzando
Regexp#match(aString)
oppure gli operatori di selezione
=~
(positivo) e !~
(negativo). Gli operatori di
selezione sono definiti per entrambi gli oggetti String
e
Regexp
. Se entrambi gli operatori delle selezione sono
Strings
, quello sulla destra sarà convertito in una espressione
regolare.
a = "Fats Waller" |
||
a =~ /a/ |
» | 1 |
a =~ /z/ |
» | nil |
a =~ "ll" |
» | 7 |
L' operatore di selezione restituisce la posizione del carattere
corrispondente al valore trovato. They also have the side effect of setting a
whole load of Ruby variables. $&
receives the part of the
string that was matched by the pattern, $`
receives the part of
the string that preceded the match, and $'
receives the string
after the match. We can use this to write a method, showRE
,
which illustrates where a particular pattern matches.
def showRE(a,re) |
||
if a =~ re |
||
"#{$`}<<#{$&}>>#{$'}" |
||
else |
||
"no match" |
||
end |
||
end |
||
|
||
showRE('very interesting', /t/) |
» | very in<<t>>eresting |
showRE('Fats Waller', /ll/) |
» | Fats Wa<<ll>>er |
La selezione setta anche le variabili $~
e $1
fino a $9
. La variabile $~
è un oggetto
MatchData
(descritta a pagina 340) che mantiene ogni cosa
potreste voler conoscere al riguardo del confronto. $1
e così
avanti mantengono i valori di parti del confronto. Approfondiremo la cosa più
avanti. E per quelle persone che si inchinano quando vedono questi nomi di
variabili sul modello del Perl, resiste in armonia. Ci saranno buone nuove
alla fine del paragrafo.
Ogni espressione regolare contiene un modello, che è utilizzata per confrontare l' espressione regolare con la stringa.
All'interno di un modello, tutti i caratteri ad eccezione di ., |, (, ), [, {, +, \, ^, $, *, e ? confrontano se stessi.
showRE('kangaroo', /angar/) |
» | k<<angar>>oo |
showRE('!@%&-_=+', /%&/) |
» | !@<<%&>>-_=+ |
Se volete confrontare uno di questi caratteri in modo letterale,
precedetelo da backslash ( \ ). Ciò spiega parte del modello che abbiamo
usato per dividere la linea della canzone, /\s*\|\s*/
. I simboli
\|
significano `confronta una barra verticale.'' Senza il
backslash, il segno ``|
''avrebbe significato
avvicendamento (che descriveremo in seguito).
showRE('yes | no', /\|/) |
» | yes <<|>> no |
showRE('yes (no)', /\(no\)/) |
» | yes <<(no)>> |
showRE('are you sure?', /e\?/) |
» | are you sur<<e?>> |
Un backslash seguito da un carattere alfanumerico è utilizzato per
introdurre un particolare costrutto di confronto, che scopriremo più avanti.
In aggiunta, un' espressione regolare può contenere un' espressione di
sostituzione#{...}
.
In generale, una espressione regolare cercherà il primo valore del modello
in una stringa. Confronta /iss/
con la stringa ``Mississippi,''
e troverà la sottostringa ``iss'' che inizia alla posizione uno. Ma come fare
se vorrete forzare un modello per confrontare solo l' inizio o la fine di una
stringa?
I modelli ^
e $
confrontano rispettivamente, l'
inizio e la fine di una linea. Questi sono spesso usati per ancorare
un modello di confronto: per esempio, /^option/
confronta la
parola ``option'' solamente se incomincia all'inizio di una riga. La sequenza
\A
confronta l' inizio di una stringa, e \z
e
\Z
confrontano la fine di una stringa. (Attualmente,
\Z
confronta la fine di una stringa a meno che la
stringa finisca con un ``\n'', nel qual caso il confronto si limita a prima
del ``\n''.)
showRE("this is\nthe time", /^the/) |
» | this is\n<<the>> time |
showRE("this is\nthe time", /is$/) |
» | this <<is>>\nthe time |
showRE("this is\nthe time", /\Athis/) |
» | <<this>> is\nthe time |
showRE("this is\nthe time", /\Athe/) |
» | no match |
Similmente, i modelli \b
e \B
confrontano
rispettivamente una parola attigua e non attigua. I caratteri della parola
sono lettere, numeri e underscore.
showRE("this is\nthe time", /\bis/) |
» | this <<is>>\nthe time |
showRE("this is\nthe time", /\Bis/) |
» | th<<is>> is\nthe time |
Una classe di caratteri è un insieme di caratteri racchiusi tra parentesi
quadre: [
caratteri]
confronta ogni singolo
carattere tra le parentesi quadre. [aeiou]
confronterà una
vocale, [,.:;!?]
confronterà la punteggiatura, e così avanti. Il
significato dell' espressione regolare di caratteri speciali
---.|()[{+^$*?
--- è chiuso all'interno delle parentesi quadre.
Comunque, la sostituzione di stringhe normali accade ancora, così (per
esempio) \b
rappresenta un carattere di backspace e
\n
uno di nuova linea (osservate la tavola 18.2 a pagina 205).
Oltre a ciò, potrete usare l' abbreviazione mostrata nella Tavola 5.1 a
pagina 62, così (per esempio) \s
confronta tutti i caratteri
matches any whitespace character, not just a literal space.
showRE('It costs $12.', /[aeiou]/) |
» | It c<<o>>sts $12. |
showRE('It costs $12.', /[\s]/) |
» | It<< >>costs $12. |
All' interno delle parentesi quadre, la sequenza c1-c2 rappresenta tutti i caratteri compresi tra c1 e c2, compresi.
Se vorrete includere il carattere letterale ]
e
-
all'interno di una classe, esso dovrà apparire all'inizio.
a = 'Gamma [Design Patterns-page 123]' |
||
showRE(a, /[]]/) |
» | Gamma [Design Patterns-page 123<<]>> |
showRE(a, /[B-F]/) |
» | Gamma [<<D>>esign Patterns-page 123] |
showRE(a, /[-]/) |
» | Gamma [Design Patterns<<->>page 123] |
showRE(a, /[0-9]/) |
» | Gamma [Design Patterns-page <<1>>23] |
Inserite il carattere ^
immediatamente dopo la parentesi
quadra di apertura per negare una classe di caratteri: [^a-z]
confronta qualsiasi carattere che non sia in minuscolo.
Alcune classi di caratteri sono utilizzate così frequentemente che Ruby ne fornisce abbreviazioni. Queste abbreviazioni sono fornite nella Tavola 5.1 a pagina 62---possono essere utilizzate indifferentemente all'interno delle parentesi quadre e del corpo del modello.
showRE('It costs $12.', /\s/) |
» | It<< >>costs $12. |
showRE('It costs $12.', /\d/) |
» | It costs $<<1>>2. |
Character class abbreviations
|
Finalmente, un punto (``.'') che appare all'esterno di una parentesi quadra rappresenta qualsiasi carattere ad eccezione della nuova linea (e nella configurazione multiline ne cerca anche una nuova linea).
a = 'It costs $12.' |
||
showRE(a, /c.s/) |
» | It <<cos>>ts $12. |
showRE(a, /./) |
» | <<I>>t costs $12. |
showRE(a, /\./) |
» | It costs $12<<.>> |
Una volta specificato il modello che scompone la linea della lista della
canzone, /\s*\|\s*/
, abbiamo detto che volevamo confrontare una
linea verticale circondata da una certa quantità di spazi liberi (caratteri
non scritti). Ora sappiamo che la sequenza \s
confronta un
singolo carattere di spazio libero, così da assomigliare al significato dell'
asterisco che in un qualche modo rappresenta ``un certo quantitativo''.
Infatti, l' asterisco rappresenta un numero di modificazioni che permettono
di confrontare multiple occorrenze di un modello.
Se r rappresenta ciò che stà prima in una espressione regolare all'interno di una modello, allora:
r* |
confronta zero o più occorrenze di r. |
r+ |
confronta una o più occorrenze di r. |
r? |
confronta nessuna o una occorrenze di r. |
r{m,n} |
confronta almeno ``m'' e al massimo ``n'' occorrenze di r. |
r{m,} |
confronta almeno ``m'' occorrenze di r. |
Questi costrutti di ripetizione hanno una grande precedenza---essi sono
tenuti solamente a precedere un' espressione regolare nel modello.
/ab+/
confronta una ``a'' seguita da una o più ``b'', non una
sequenza di ``ab''. Dovete stare attenti anche con i costrutti *
---il modello /a*/ confronterà qualsiasi stringa; ogni stringa ha nessun o
più ``a''.
Questi modelli sono chiamati greedy, perchè di default confronteranno tutto quello che potranno della stringa. Potrete modificare questa caratteristica, e dovendo confrontarle il minimo, aggiungendo un segno di punto esclamativo.
a = "The moon is made of cheese" |
||
showRE(a, /\w+/) |
» | <<The>> moon is made of cheese |
showRE(a, /\s.*\s/) |
» | The<< moon is made of >>cheese |
showRE(a, /\s.*?\s/) |
» | The<< moon >>is made of cheese |
showRE(a, /[aeiou]{2,99}/) |
» | The m<<oo>>n is made of cheese |
showRE(a, /mo?o/) |
» | The <<moo>>n is made of cheese |
Sappiamo che la barra verticale è speciale, poichè la nostra linea divide il modello because our line splitting pattern had to escape it with a backslash. Questo perchè That's because an unescaped vertical bar ``|'' confronta sia le espressioni regolari che precedono e sia quelle che seguono.
a = "red ball blue sky" |
||
showRE(a, /d|e/) |
» | r<<e>>d ball blue sky |
showRE(a, /al|lu/) |
» | red b<<al>>l blue sky |
showRE(a, /red ball|angry sky/) |
» | <<red ball>> blue sky |
C' è una trappola per l' imprudente (programmatore), poichè ``|'' possiede una bassa precedenza. L' ultimo esempio sopra confronta ``red ball'' o ``angry sky'', non ``red ball sky'' o ``red angry sky''. Per confrontare ``red ball sky'' o ``red angry sky'', avrete bisogno di sovrascrivere la precedenza di default utilizzando il grouping.
Potrete usare le parentesi per raggruppare termini all'interno di un' espressione regolare. Ogni cosa all'interno di un gruppo è trattata come una singola espressione regolare.
showRE('banana', /an*/) |
» | b<<an>>ana |
showRE('banana', /(an)*/) |
» | <<>>banana |
showRE('banana', /(an)+/) |
» | b<<anan>>a |
a = 'red ball blue sky' |
||
showRE(a, /blue|red/) |
» | <<red>> ball blue sky |
showRE(a, /(blue|red) \w+/) |
» | <<red ball>> blue sky |
showRE(a, /(red|blue) \w+/) |
» | <<red ball>> blue sky |
showRE(a, /red|blue \w+/) |
» | <<red>> ball blue sky |
showRE(a, /red (ball|angry) sky/) |
» | no match |
a = 'the red angry sky' |
||
showRE(a, /red (ball|angry) sky/) |
» | the <<red angry sky>> |
Le parentesi sono usate anche per raccogliere i risultati di un modello di
ricerca. Ruby conta le parentesi aperte, e per ognuno salva il risultato
della ricerca parziale tra esso ed la corrispondente chiusura di parentesi.
Potrete usare questa parziale ricerca sia all'interno del resto del modello,
sia nel vostro programma in Ruby. All'interno del modello, la sequenza
\1
si riferisce alla ricerca del primo gruppo, \2
al secondo, e così via. All'esterno del modello, le speciali variabili
$1
, $2
, e così via, servono allo stesso intento.
"12:50am" =~ /(\d\d):(\d\d)(..)/ |
» | 0 |
"Hour is #$1, minute #$2" |
» | "Hour is 12, minute 50" |
"12:50am" =~ /((\d\d):(\d\d))(..)/ |
» | 0 |
"Time is #$1" |
» | "Time is 12:50" |
"Hour is #$2, minute #$3" |
» | "Hour is 12, minute 50" |
"AM/PM is #$4" |
» | "AM/PM is am" |
L' abilità di usare parte del confronto corrente in seguito nel ricerca, vi permette di cercare varie forme di ripetizione.
# match duplicated letter |
||
showRE('He said "Hello"', /(\w)\1/) |
» | He said "He<<ll>>o" |
# match duplicated substrings |
||
showRE('Mississippi', /(\w+)\1/) |
» | M<<ississ>>ippi |
Potrete usare anche riferimenti precedenti per confrontare i delimitatori.
showRE('He said "Hello"', /(["']).*?\1/) |
» | He said <<"Hello">> |
showRE("He said 'Hello'", /(["']).*?\1/) |
» | He said <<'Hello'>> |
Talvolta cercare un modello all'interno di una stringa è abbastanza buono.
Se un amico vi sfida a cercare una parola che contiene le lettere a, b, c, d,
e in ordine, potreste cercare una lista di parole con il modello
/a.*b.*c.*d.*e/
e trovare ``absconded (latitanza)'' e
``ambuscade (imboscata)''. Questo deve valere qualcosa.
In qualche modo, esistono volte in cui vorreste cambiare le cose basate su un modello di ricerca. Ritorniamo al nostro file della lista di canzoni. Chiunque lo abbia creato ha inserito tutti i nomi degli artisti in minuscolo. Nel momento in cui li mostreremo sul display del jukebox, starebbero meglio in una forma mista. Come possiamo cambiare la prima lettera di ogni parola in maiuscolo?
Il metodo String#sub
e String#gsub
cerca una
porzione di una stringa che confronti il loro primo argomento e lo
sostituisca con il secondo. String#sub
esegue una
sostituzione, finchè String#gsub
rimpiazzi
ogni occorrenza del confronto. Entrambe le routine restituiscono una nuova
copia di String
che contiene le sostituzioni. Le versioni mutate
String#sub!
e String#gsub!
modificano la stringa originale.
a = "the quick brown fox" |
||
a.sub(/[aeiou]/, '*') |
» | "th* quick brown fox" |
a.gsub(/[aeiou]/, '*') |
» | "th* q**ck br*wn f*x" |
a.sub(/\s\S+/, '') |
» | "the brown fox" |
a.gsub(/\s\S+/, '') |
» | "the" |
Il secondo argomento di entrambe le funzioni può essere o
String
oppure un blocco. Se viene usato un blocco, il valore
verrà sostituito in String
.
a = "the quick brown fox" |
||
a.sub(/^./) { $&.upcase } |
» | "The quick brown fox" |
a.gsub(/[aeiou]/) { $&.upcase } |
» | "thE qUIck brOwn fOx" |
Così, questo assomiglia alla risposta per convertire il nome dell'
artista. Il modello che confronta il primo carattere di una parola è
\b\w
---cerca una parola di confine seguita da un carattere di
una parola . Combinatelo con gsub
e potremo modificare il nome
dell' artista.
def mixedCase(aName) |
||
aName.gsub(/\b\w/) { $&.upcase } |
||
end |
||
|
||
mixedCase("fats waller") |
» | "Fats Waller" |
mixedCase("louis armstrong") |
» | "Louis Armstrong" |
mixedCase("strength in numbers") |
» | "Strength In Numbers" |
Precedentemente abbiamo notato che la sequenza \1
,
\2
, e così avanti, sono disponibili nel modello, rappresentando
il gruppo confrontato precedentemente. Le stesse sequenze sono disponibili
nel secondo argomento di sub
e gsub
.
"fred:smith".sub(/(\w+):(\w+)/, '\2, \1') |
» | "smith, fred" |
"nercpyitno".gsub(/(.)(.)/, '\2\1') |
» | "encryption" |
esistono sequenze addizionali di backslash che lavorano nella sostituzione
delle stringhe: \&
(ultimo confronto), \+
(ultimo gruppo confrontato), \`
(stringa precedente da
confrontare), \'
(stringa dopo il confronto), e \\
(un backslash nel senso letterale). Cio vi confonde se vorrete includere un
backslash in una sostituzione. La cosa ovvia è scrivere:
str.gsub(/\\/, '\\\\') |
Chiaramente, questo codice stà tentando di sostituire ogni backslash in
str
con due. Il programmatore ha raddoppiato il backslashes nel
testo della sostituzione, sapendo che sarebbero stati convertiti in
``\\
'' nell'analisi sintattica. Comunque, quando la sostituzione
viene eseguita, il motore dell' espressione regolare esegue un altro
passaggio attraverso la stringa, convertendo ``\\
'' in
``\
'', così l' effetto risultante è quello di sostituire ogni
singolo backslash con un altro backslash. Avrete bisogno di scrivere
gsub(/\\/, '\\\\\\\\')
!
str = 'a\b\c' |
» | "a\b\c" |
str.gsub(/\\/, '\\\\\\\\') |
» | "a\\b\\c" |
Comunque, sapendo che \&
è sostituito con la stringa di
confronto, potrete anche scrivere
str = 'a\b\c' |
» | "a\b\c" |
str.gsub(/\\/, '\&\&') |
» | "a\\b\\c" |
Se utilizzate il blocco formale gsub
, la stringa per la
sostituzione è analizzata solo una volta (durante il passaggio sintattico) ed
il risultato è ciò che desiderate.
str = 'a\b\c' |
» | "a\b\c" |
str.gsub(/\\/) { '\\\\' } |
» | "a\\b\\c" |
Alla fine, come esempio della meravigliosa espressività di combinare le
espressione regolari con i blocchi di codice, considerate il seguente
frammento di codice del modulo di libreria CGI, scritto da Wakou Aoyama. Il
codice prende una stringa contenente sequenze di escape in HTML e lo converte
in normale codice ASCII. Poichè è stato scritto per i giapponesi, usa il
modificatore ``n'' nell'espressione regolare, che taglia fuori which turns
off wide-character processing. Illustra anche un caso
di un
espressione di Ruby, di cui abbiamo discusso all'inizio di pagina 83.
def unescapeHTML(string) str = string.dup str.gsub!(/&(.*?);/n) { match = $1.dup case match when /\Aamp\z/ni then '&' when /\Aquot\z/ni then '"' when /\Agt\z/ni then '>' when /\Alt\z/ni then '<' when /\A#(\d+)\z/n then Integer($1).chr when /\A#x([0-9a-f]+)\z/ni then $1.hex.chr end } str end puts unescapeHTML("1<2 && 4>3") puts unescapeHTML(""A" = A = A") |
produce:
1<2 && 4>3 "A" = A = A |
Dobbiamo ammettere che mentre tutte queste misteriose variabili sono molto utili da usare, esse non sono molto object oriented, e sono oltretutto criptiche. Mentre non avevamo detto che ogni cosa in Ruby è un oggetto? Che cosa c' è di sbagliato?
In realtà nulla. E' solamente che quando Matz ha disegnato Ruby, ha prodotto un sistema di manipolazione delle espressioni regolari completamente object-oriented. Poi lo rese familiare ai programmatori in Perl avviluppandolo completamente di tutte queste $-variabili sopra di esso. Gli oggetti e le classi sono ancora lì, sotto la superficie. Così passiamo un pò di tempo per mostrarle.
Siamo già passati attraverso una classe: le lettere di espressioni
regolari creano istanze di classe Regexp
(documentate a partire
da pagina 366).
re = /cat/ |
||
re.type |
» | Regexp |
Il metodo Regexp#match
confronta un' espressione regolare con una stringa. Se non esistono valori,
il metodo restituisce nil
. Al contrario, restituisce un instanza
della classe MatchData
, documentata a partire da pagina 340. E
che l' oggetto MatchData
vi fornisce l' accesso a tutte le
informazioni disponibili sul confronto. Tutte queste buone cose che ottenete
dalle $-variabili è raccolto in un pratico piccolo oggetto.
.
re = /(\d+):(\d+)/ # match a time hh:mm |
||
md = re.match("Time: 12:34am") |
||
md.type |
» | MatchData |
md[0] # == $& |
» | "12:34" |
md[1] # == $1 |
» | "12" |
md[2] # == $2 |
» | "34" |
md.pre_match # == $` |
» | "Time: " |
md.post_match # == $' |
» | "am" |
Poichè i dati del confronto sono conservati nel suo proprio oggetto,
potrete ottenere il risultato di due o più modelli di confronto allo stesso
tempo, cosa che non potreste usando le $-variabili. Nel prossimo esempio, lo
constatiamo confrontando il medesimo oggetto Regexp
con due
stringhe. Ogni confronto restituisce un unico oggetto MatchData
,
che verifichiamo esaminando i due campi dei sottomodelli.
.
re = /(\d+):(\d+)/ # match a time hh:mm |
||
md1 = re.match("Time: 12:34am") |
||
md2 = re.match("Time: 10:30pm") |
||
md1[1, 2] |
» | ["12", "34"] |
md2[1, 2] |
» | ["10", "30"] |
Così come fanno le $-variabili a sistemarlo? Bè, dopo ogni modello di
confronto, Ruby salva un riferimento al risultato (nil
o un
oggetto MatchData
) in una variabile thread-local (accessibile
usando $~
). Tutte le altre variabili di espressioni regolari
sono poi derivate da questo oggetto. Nonostante ciò non possiamo realmente
pensare di usare il seguente codice, esso dimostra che tutte le altre
MatchData
-related $-variabili sono in realtà schiavizzate al
valore in $~
.
re = /(\d+):(\d+)/ |
||
md1 = re.match("Time: 12:34am") |
||
md2 = re.match("Time: 10:30pm") |
||
[ $1, $2 ] # last successful match |
» | ["10", "30"] |
$~ = md1 |
||
[ $1, $2 ] # previous successful match |
» | ["12", "34"] |
Avendo detto tutto ciò, dobbiamo 'fess up. Andy e Dave normalmente usano
le $-variabili piuttosto che preoccuparsi degli oggetti
MatchData
. Per l' uso quotidiano, lo ritengono più conveniente.
Talvolta non riusciamo ad essere pragmatici.
Precedente < | Indice ^ |
Prossimo
> |
Extracted from the book "Programming Ruby - The Pragmatic Programmer's
Guide"
Copyright © 2000 Addison Wesley Longman, Inc. Released under the terms of the
Open Publication License
V1.0.
This reference is available for download.