FIGYELEM! Ez a dokumentum kizárólag az ELTE IK hallgatók számára oktatási célra készült! Félkész munka, dolgozunk rajta! Terjeszteni, felhasználni máshol a szerzők engedélye nélkül tilos! |
|
Standard típusok
Eddig azzal szórakoztunk, hogy összerakjuk zenegépünk kódjának egyes részeit. Találkoztunk tömbökkel, hash táblákkal és processzekkel, de még nem érintettük igazából a többi alapvető Ruby típust: számok, stringek, tartományok és reguláris kifejezések. Egy pár oldalon keresztül áttekintjük most a nyelv ezen fontos építőkockáit.
Számok
A Ruby egész és lebegőpontos számokat egyaránt támogat. Az egész számok bármekkorák lehetnek (ennek mértékét a szabad memória szabja meg). Bizonyos intervallumokon belüli egészek (-230..230-1 vagy -262..262-1 intervallumok elemei) bináris formában tároltak és a Fixnum osztály példányai. Az ennél nagyobb egészek a Bignum osztály elemei. A Ruby automatikusan kezeli a számok megfelelő tárolását és konvertálását a két nagy típus között.
num = 8
|
|
7.times do
|
|
print num.class, " ", num, "\n"
|
|
num *= num
|
|
end
|
|
|
fenti példa a következő kimenetet generálja:
Fixnum 8
|
|
Fixnum 64
|
|
Fixnum 4096
|
|
Fixnum 16777216
|
|
Bignum 281474976710656
|
|
Bignum 79228162514264337593543950336
|
|
Bignum 6277101735386680763835789423207666416102355444464034512896
|
|
|
Ha más számrendszerekben kívánjuk a számot megadni, akkor azt a megfelelő prefixszel megtehetjük: 0 - nyolcas (oktális), 0x - tizenhatos (hexadecimális) és 0b - bináris.
123456
|
# Fixnum
|
123_456
|
# Fixnum (aláhúzás érdektelen)
|
-543
|
# negatív Fixnum
|
123_456_789_123_345_789
|
# Bignum
|
0xaabb
|
# hexadecimális
|
0377
|
# oktális
|
-0b101_010
|
# bináris (negált)
|
|
Karakterek és escape szekvenciák(???) egész értékű megfelelőjét az azt megelőző kérdőjel kitételével kapjuk meg. Control és meta kombinációkat is generálhatunk a ?\C-x, ?\M-x, and ?\M-\C-x formák használatával. A control változat megegyezik az ertek & 0x9f , a meta változat pedig az ertek | 0x80 kifejezéssel.
?a
|
# karakter kódja
|
?\n
|
# új sor karakter kódja (0x0a)
|
?\C-a
|
# control a = ?A & 0x9f = 0x01
|
?\M-a
|
# a meta beállítja a hetes bitet
|
?\M-\C-a
|
# meta és control a
|
?\C-?
|
# törlés karakter
|
|
Azok a numerikus literálok, melyek tizedespontot és/vagy kitevőt tartalmaznak, Float (lebegőpontos) objektumok lesznek, illeszkedve a rendszer architektúra decimal adattípusához. A tizedespontot követnie kell legalább egy számjegynek, mivel a 1.e3 kifejezés kiértékeléskor a Fixnum típusú objektum e3 metódusát hívnánk meg.
Minden szám objektum és számos üzenetre reagálnak. Így pl. a C++-tól eltérően abszolút értéket aNumber.abs módon számolhatunk a abs(aNumber) helyett.
Az egész számok számos hasznos iterátort is tartalmaznak. A 7.times -t már láttuk egyik előző példánkban. A upto , downto iterátorok felfele ill. lefele lépdelve iterálnak két egész szám között, a step pedig hagyományos for ciklusokhoz használható.
3.times { print "X " }
|
|
1.upto(5) { |i| print i, " " }
|
|
99.downto(95) { |i| print i, " " }
|
|
50.step(80, 5) { |i| print i, " " }
|
|
|
példa lefutása a következőt eredményezi:
X X X 1 2 3 4 5 99 98 97 96 95 50 55 60 65 70 75 80
|
|
|
Végül a Perl programozók számára egy figyelmeztetés. Számot tartalmazó stringek nem konvertálódnak automatikusan számokká kifejezésekben. Ez leggyakrabban akkor okozhat gondot, amikor számokat olvasunk fájlból. A következő példa valószínűleg nem azt fogja tenni, mint amire számítunk.
DATA.each do |line|
|
|
vals = line.split
|
# sor szétvágása szavakra, melyek a vals tömbbe kerülnek
|
print vals[0] + vals[1], " "
|
|
end
|
|
|
Adjuk meg neki bemenetként a következőt:
Lefuttatva ezt adja vissza: "34 56 78". Mi történt?
A probléma ott volt, hogy a program nem számokat, hanem stringeket olvasott be és a plusz operátor összeadás helyett összefűzte a két változó értékét. Ezt a viselkedést a String#to_i metódussal javíthatjuk ki.
DATA.each do |line|
|
|
vals = line.split
|
|
print vals[0].to_i + vals[1].to_i, " "
|
|
end
|
|
|
program eredménye:
Stringek
A Ruby stringek egyszerűen 8 bit hosszú bájtok sorozata. Általában olvasható karaktereket tartalmaznak, de ez nem kötelező. String tartalmazhat bináris adatot is. A stringek osztálya a String .
Stringeket gyakran string literálokból készítünk - ezek karaktersorozatok határolójelek között. Mivel bináris adatot másképp nehézkes program forrásban ábrázolni, ezért lehetőségünk van erre ún. escape szekvenciákat (karakter, mely előtt backslash jel áll) elhelyeznünk string literálban, melyek futáskor kicserélődnek bináris értékükre. A string határoló típusa dönti el az elvégzendő helyettesítések mélységét. Az aposztrófok közé zárt literál esetében két egymást követő backslash (\) egyre, backslash utáni aposztróf jel simán aposztróf jelre lesz cserélve.
'escape using "\\"'
|
» escape using "\"
|
'That\'s right'
|
» That's right
|
|
Idézőjel közé zárt stringek rakás escape szekvenciát támogatnak. A leggyakoribb ilyen az \n új sor jel. [ teljes lista ]. Továbbá a stringbe ágyazott #{kifejezés} segítségével lehetőségünk nyílik Ruby kifejezések behelyettesítésére. Globális, osztály és példány változók esetében a zárójelek elhagyhatók.
"Seconds/day: #{24*60*60}"
|
» Seconds/day: 86400
|
"#{'Ho! '*3}Merry Christmas"
|
» Ho! Ho! Ho! Merry Christmas
|
"This is line #$."
|
» This is line 3
|
|
Még további három módunk van string literálokat konstruálni: %q , %Q és "ide jön a szöveg".
%q és %Q nyitnak meg aposztrófos, ill. idézőjeles stringeket a következő módon:
%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
|
|
A %q és %Q után jövő jel lesz a határoló jele a stringnek. Ha ez egy sima, szögletes vagy kapcsos nyitó zárójel, illetve < jel, akkor értelemszerűen a string a jel párjáig tart. Egyébként pedig a következő ugyanolyan határoló jelig tart.
Végezetül létrehozhatunk stringet az ide jön a szöveg módon:
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
|
|
|
Az ide jön a szöveg technika sorokból áll egészen addig, amíg a lezáró string következik. Ezt a << karakterek után adhatjuk meg. Normális esetben a lezáró stringnek az első oszlopban kell kezdődnie, ezt azonban módosíthatjuk úgy, hogy a << karakterek mögé - (mínusz) jelet írunk. Ebben az esetben behúzhatjuk a lezáró stringet egy bentebbi oszlopig.
print <<-STRING1, <<-STRING2
|
|
Concat
|
|
STRING1
|
|
enate
|
|
STRING2
|
|
|
a következőt jelenti:
Hogyan használjuk a stringeket?
A String talán a legnagyobb beépített osztálya a Ruby-nak, több mint 75 standard metódussal. Nem fogjuk az összeset átnézni, a library reference-ben(!!!) megtalálható a teljes lista. Ehelyett megnézünk néhány szokásos string kifejezést - olyanokat, melyeket nap mint nap használunk.
Térjünk vissza a zenegép példánkhoz. Habár úgy terveztük, hogy az az Internethez csatlakozzon, számos népszerű slágert lokálisan is tárol. Azaz, még ha ördög bújik is a háló madzagba, akkor is képesek leszünk ügyfeleinket szórakoztatni.
Történelmi okokból (létezik más?) a számok listáját text fájl soraiban fogjuk tárolni. Minden sor a nótához tartozó fájl nevét, időbeli hosszát, a szerző nevét és a dal címét fogja tartalmazni függőleges vonallal ("|" jel) elválasztva. Például:
/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
|
|
: : : :
|
|
|
Megvizsgálva az adatokat az teljesen tiszta, hogy a String osztály számos metódusa közül néhányat arra fogunk használni, hogy adatokat nyerjünk ki a fenti fájlból, majd azokkal a Song példányokat töltsünk ki. Amikre minimum szükségünk lesz:
- a sorok mezőkre szabdalása
- a lejátszás idejének átkonvertálása mm:ss formáról másodpercekre
- a szerző nevéről a fölösleges szóközök eltávolítása
Az első dolgunk a sorok mezőkre bontása lesz. Ezt a String#split metódusával tehetjük meg a legelegánsabban. Ebben az esetben a split -et a következő reguláris kifejezéssel paraméterezzük meg: /\s*\|\s*/ , ami azt jelenti, hogy a mezőket elválasztó szöveg egy függőleges vonal két oldalán tetszőleges számú (akár üres) szóközzel. A sorokat új sor karakter választja el egymástól, ezeket String#chomp segítségével távolíthatjuk el a sor szétbontása előtt.
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]
|
|
|
a következőt eredményezi:
Song: Wonderful World--Louis Armstrong (2:58)
|
|
|
Zenegépünk tud kulcsszavakra keresni, dal címe vagy szerző nevéből egy szó alapján kilistázza az összes talált számot. Írd be azt, hogy "fats" és meg fogja találni például Fats Domino, Fats Navarro vagy Fats Waller számait. Ezt úgy valósítjuk meg, hogy létrehozunk egy kulcsszó indexelő osztályt. Paraméterként megadott objektum és pár string alapján leindexeli az adott objektumot minden szóra (kettő vagy több karakter esetén) minden egyező esetre. Ez illusztrálni fogja a String osztály számos metódusának újabb néhányát.
class WordIndex
|
|
def initialize
|
|
@index = Hash.new(nil)
|
|
end
|
|
def index(anObject, *phrases)
|
|
phrases.each do |aPhrase|
|
|
aPhrase.scan /\w[-\w']+/ do |aWord|
|
# extract each word
|
aWord.downcase!
|
|
@index[aWord] = [] if @index[aWord].nil?
|
|
@index[aWord].push(anObject)
|
|
end
|
|
end
|
|
end
|
|
def lookup(aWord)
|
|
@index[aWord.downcase]
|
|
end
|
|
end
|
|
|
A String#scan kigyűjti a reguláris kifejezés szerint illeszkedő (megtalált) stringeket. Esetünkben a \w[-\w']+ minta olyanokat keres, amelyek egy szavakban megtalálható betű kezd, majd egy vagy több, szögletes zárójel között található karakter követ (kötőjel, karakter szóban, aposztróf). Ahhoz, hogy a keresésünk mind a kisbetűket, mind a nagybetűket megtalálja, a keresendő szavakat és az aktuális stringet is kisbetűsítjük a String#downcase! metódussal. Ügyeljünk a metódus végén található felkiáltójelre! Hasonlóan a squeeze! metódushoz, a végén található felkiáltójel azt jelenti, hogy a művelet helyben végződik el és nem egy transzformált objektumot ad vissza.
Megjegyzés: a programban van egy hiba. A Gone, gone, gone című dal háromszor lenne leindexelve. Van ötleted arra, hogy kellene kijavítani?
Most kibővítjük a SongList osztályt úgy, hogy minden hozzáadott dalt indexeljen le és adunk még egy új metódust is hozzá, amely megkeresi a megadott szót
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
|
|
|
Végezetül leteszteljük az egészet.
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")
|
|
|
Lefuttatva:
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)
|
|
|
A következő 50 oldalt is meg tudnánk tölteni a String osztály metódusainak kitárgyalásával. De menjünk most tovább egy egyszerűbb adattípushoz, a tartományhoz.
Tartományok
Tartományok, intervallumok mindenhol megtalálhatóak: januártól decemberig, nullától kilencig, a nyerstől a teljesen készig, vonal vastagság 50-től 67-ig, stb. Ha a Ruby-t arra találták ki, hogy segítsen a valóság modellezésében, akkor teljesen természetes, hogy támogatja ezeket az intervallumokat. A Ruby azonban ennél eggyel tovább megy: jelenleg a tartományokat három dologra használja: sorozatok, feltételek és intervallumok.
Tartományok, mint sorozatok
A tartományok első és talán a legtermészetesebb alkalmazása egy sorozat kifejezése. A sorozatoknak kezdete és vége van és mód arra, hogy a sorozat rákövetkező elemét előállítsuk. Ruby-ban ilyen sorozatokat a .. és ... intervallum operátorok segítségével hozhatunk létre. A kétpontos változat mindkét végén zárt, a hárompontos a jobb végénél nyílt (azaz a jobb oldali szám már nincs benne az intervallumban) intervallumot hoz létre.
1..10
|
|
'a'..'z'
|
|
0...anArray.length
|
|
|
A Perl korábbi változataitól eltérően Ruby-ban a tartományok nem listás ábrázolásúak: az 1..100000 sorozat Range objektumban tárolt, amiben referenciák mutatnak Fixnum típusú számokra. Ha mégis listás formára van szükségünk, azt a to_a metódus előállítja.
(1..10).to_a
|
» [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
('bar'..'bat').to_a
|
» ["bar", "bas", "bat"]
|
|
A tartományok rendelkeznek iterátor metódusokkal, melyek segítségével bejárhatjuk adatszerkezetüket különféle módokon.
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
|
|
|
Eddig megnéztük számokból és stringekből alkotott tartományokat. Azonban, ahogy ez elvárható egy objektum-orientált nyelvtől, Ruby-ban alkothatunk egyedi objektum tartományokat is. Az egyetlen megkötés, melyet minden tartománybeli objektumnak tudni kell, az a succ rákövetkezési operátor, amely mindig megadja egy sorozatban az adott objektum rákövetkezőjét. Továbbá a sorozatnak, mint halmaznak jólrendezettnek kell lennie, azaz minden objektum összehasonlítható a másikkal a <=> kisebb, nagyobb vagy egyenlő operátor segítségével. A néha űrhajó operátornak hívott <=> összehasonlítja operandusait és -1, 0 vagy 1-et ad vissza attól függően, hogy a bal oldali operandus kisebb, egyenlő vagy nagyobb a jobb oldalinál.
Ím egy egyszerű osztály, amely # jelek sorait reprezentálja. Ezt fogjuk használni, mint szöveges alapú kódtörzset a hangerő szabályozó ellenőrzéséhez.
class VU
|
|
|
|
include Comparable
|
|
|
|
attr :volume
|
|
|
|
def initialize(volume)
|
# 0..9
|
@volume = volume
|
|
end
|
|
|
|
def inspect
|
|
'#' * @volume
|
|
end
|
|
|
|
# Intervallumok támogatásához
|
|
|
|
def <=>(other)
|
|
self.volume <=> other.volume
|
|
end
|
|
|
|
def succ
|
|
raise(IndexError, "Túl nagy hangerő") if @volume >= 9
|
|
VU.new(@volume.succ)
|
|
end
|
|
end
|
|
|
Most ezt leteszteljük VU objektumokból készített tartományon.
medium = VU.new(4)..VU.new(7)
|
|
medium.to_a
|
» [####, #####, ######, #######]
|
medium.include?(VU.new(3))
|
» false
|
|
Tartományok mint feltételek
Sorozatok ábrázolása mellett a tartományok feltételes kifejezésként is használhatóak. Például a következő kódrészlet sorok azon halmazát írja ki a standard bemenetről olvasva.. ahol az első sor "start"-tal, az utolsó "end"-del végződik.
while gets
|
|
print if /start/../end/
|
|
end
|
|
|
A kulisszák mögött a tartomány nyomon követi minden egyes teszt eredményét. Erre majd még később találunk példákat.
Tartományok, mint intervallumok
Utolsó alkalmazásként megemlítjük a tartományokat, mint intervallum vizsgálat eszközét, tartalmazza-e az intervallum a kérdéses értéket? Ezt a === elemenkénti vizsgálat operátorral tehetjük meg.
(1..10) === 5
|
» true
|
(1..10) === 15
|
» false
|
(1..10) === 3.14159
|
» true
|
('a'..'j') === 'c'
|
» true
|
('a'..'j') === 'z'
|
» false
|
|
Reguláris kifejezések
Ha visszalapozunk az előző oldalakra, ahol dal listát hoztunk létre fájlból beolvasva. A beolvasáskor reguláris kifejezést használtunk a sorok mezőkre bontásához. Azt állítottuk, hogy a line.split(/\s*\|\s*/) kifejezés függőleges vonalhoz és az azt esetleg körbevevő szóközökre illeszkedik. Most mélyedjünk a reguláris kifejezésekben jobban és nézzük meg, miért igaz állításunk.
A reguláris kifejezéseket mintaillesztésre használjuk. A Ruby beépített támogatást nyújt ahhoz, hogy a mintaillesztést kényelmessé teszi. Ebben a fejezetben áttekintjük a reguláris kifejezések főbb tulajdonságait. Néhány most kimarad, azok megtalálhatóak későbbi oldalakon (!!!).
A reguláris kifejezések Regexp típusúak, létrehozni őket konstruktorral vagy a /kifejezés/, ill. %r\kifejezés\ literálokkal tudjuk.
a = Regexp.new('^\s*[a-z]')
|
» /^\s*[a-z]/
|
b = /^\s*[a-z]/
|
» /^\s*[a-z]/
|
c = %r{^\s*[a-z]}
|
» /^\s*[a-z]/
|
|
Ha létrehoztuk reguláris kifejezésünket, stringhez illeszteni a Regexp#match(aString) , =~ (pozitív illeszkedés) vagy !~ (negatív illeszkedés) operátorral tudjuk. Az illeszkedés operátorok a String és Regexp osztályokban definiáltak, a jobb oldali kifejezés reguláris kifejezéssé lesz konvertálva.
a = "Fats Waller"
|
|
a =~ /a/
|
» 1
|
a =~ /z/
|
» nil
|
a =~ "ll"
|
» 7
|
|
Az illesztő operátorok az illesztés karakter pozicíóját adják vissza, továbbá beállítanak egy csomó Ruby változót mellékhatásként. $& tartalmazza a stringnek azon részét, amely illeszkedik a mintához, $` a megelőző, $' pedig az azt követő string darabot tartalmazza. Ezeket felhasználhatjuk például a showRE metódus írásához, amely jól illusztálja adott minta illeszkedését.
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
|
|
Az illesztés beállítja még a $~ , $1 , ..., $9 változókat is, melyek az adott szálra globálisak. A $~ változó MatchData típusú és minden olyan információt tartalmaz, amit tudni akarunk az illesztésünkről. A $1 , ... pedig rész illesztéseket tartalmazzák - erről majd később beszélünk még. A fejezet végén pedig akad egy jóhírünk azoknak, akik mélyen meghajolnak a Perl-szerű változónevek előtt.
Minták
Minden reguláris kifejezés tartalmaz mintát, mely segítségével illeszthető a kifejezés stringhez.
Mintán belül minden karakter önmagára illeszkedik, kivéve a ., |, (, ), [, {, +, \, ^, $, *, és ? karaktereket.
showRE('kangaroo', /angar/)
|
» k<<angar>>oo
|
showRE('!@%&-_=+', /%&/)
|
» !@<<%&>>-_=+
|
|
Ha mégis illeszteni kívánjuk a fent megadott speciális karakterek bármelyikét, akkor elé egy \ (backslash) karaktert kell írnunk. Ez megmagyarázza azon mintánk részét, melyet a dal rekordok mezőinek szétválasztásához használtunk: /\s*\|\s*/ . A \| jelentése "függőleges vonal illesztése", backslash nélkül a | jel alternatívát jelent (erről később lesz szó bővebben).
showRE('yes | no', /\|/)
|
» yes <<|>> no
|
showRE('yes (no)', /\(no\)/)
|
» yes <<(no)>>
|
showRE('are you sure?', /e\?/)
|
» are you sur<<e?>>
|
|
Ha backslash-t alfanumerikus karakter követi, annak speciális jelentése van, melyről később lesz szó. Továbbá a reguláris kifejezés tartalmazhat #{...} helyettesítő kifejezést is.
Horgonyok
Alapértelmezés szerint a reguláris kifejezések megpróbálják megtalálni az első illeszkedést a stringhez. Illesszük /iss/ mintát a "Mississippi" stringhez, az illeszkedni fog az "iss"-hez az egyes pozíción. De mi van akkor, ha konkrétan a string elejéhez vagy végéhez akarunk illeszteni?
A ^ és $ minták a string elejére, ill. végére illesztenek. Ezt gyakran használják illesztések rögzítésére, például a /^option/ akkor illeszkedik stringhez, ha az "option" szóval kezdődik. A \A illeszkedik minden olyan string kezdetéhez, a \z vagy \Z pedig a végéhez (pontosabban a \z akkor illeszkedik a string végéhez, ha az nem végződik \n karakterrel, ekkor e jel előttig illeszkedik).
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
|
|
Hasonlóan, a \b és \B szavak végződéséhez illeszkednek. Szó itt betűkből, számokból és aláhúzás karakterből állhat.
showRE("this is\nthe time", /\bis/)
|
» this <<is>>\nthe time
|
showRE("this is\nthe time", /\Bis/)
|
» th<<is>> is\nthe time
|
|
Karakter osztályok
Karakter osztál karakterek halmaza szögletes zárójelek között megadva: [ karakterek] minta illeszkedik minden olyan karakterre, amelyik ennek az osztálynak eleme. [aeiou] magánhangzókhoz, [,.:;!?] központozáshoz illeszkedik, és így tovább. A speciális reguláris kifejezésbeli karakterek jelentősége a szögletes zárójele között megszűnik, viszont a szokásos string helyettesítések megmaradnak, így például a \b a backslash karaktert, a \n az új sor karaktert jelöli. Vannak továbbá előre definiált osztálynév rövidítések, melyeket használhatunk. A \s a helykitöltő (whitespace) karakterek osztályát jelöli.
showRE('It costs $12.', /[aeiou]/)
|
» It c<<o>>sts $12.
|
showRE('It costs $12.', /[\s]/)
|
» It<< >>costs $12.
|
|
A zárójeleken belül a c1-c2 intervallum jelölés jelenti a c1 és c2 közti összes karakter halmazát.
Ha szeretnénk olyan literálokat is belevenni az osztályba, mint ] és - , akkor azokat előre kell vennünk.
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]
|
|
Karakter osztály rövidítések
Sorozat |
Jelölés |
Jelentés |
\d |
[0-9] |
számjegyek |
\D |
[^0-9] |
nem számjegy |
\s |
[\s\t\r\n\f] |
helykitöltők |
\S |
[^\s\t\r\n\f] |
nem helykitöltők |
\w |
[A-Za-z0-9_] |
szó-karakter |
\W |
[^A-Za-z0-9_] |
nem szó-karakter |
 |
|
|
Végezetül, a zárójeleken kívüli pont karakter szimbolizál minden karaktert, kivéve az új sort (többsoros mód esetén az új sort is jelenti).
a = 'It costs $12.'
|
|
showRE(a, /c.s/)
|
» It <<cos>>ts $12.
|
showRE(a, /./)
|
» <<I>>t costs $12.
|
showRE(a, /\./)
|
» It costs $12<<.>>
|
|
Ismétlések
Amikor meghatároztuk azt a mintát, amely darabokra szedte a dallista sorát, /\s*\|\s*/ , akkor azt mondtuk, a függőleges vonalat és az azt körbevevő szóközöket szeretnénk illeszteni. Most már tudjuk, a \s egy helykitöltő karakterre illeszt, így úgy tűnik, hogy az ezt követő csillag jel valahogy a "bármennyit" jelenti. Tény, a csillag jel egy a mennyiségi módosítók közül, melyek segítségével többszörös előfordulásra is illeszthetünk egy mintában.
Ha r közvetlenül reguláris kifejezés után áll, akkor:
r*
|
Nulla vagy több előfordulásra illeszt.
|
r+
|
Egy vagy több előfordulásra illeszt.
|
r?
|
Nulla vagy egy előfordulásra illeszt (opcionalitás).
|
r{m,n}
|
Legalább m, legfeljebb n előfordulásra illeszt.
|
r{m,}
|
Legalább m előfordulásra illeszt.
|
|
Alternálás
Már tudjuk, hogy a függőleges vonal speciális, ha illesztéshez akarjuk használni, akkor a backslash karaktert kell eléraknunk. Ez azért van, mert ez a jel, | jelenti az alternálást reguláris kifejezésekben. Jelentése: a vonal előtti részre vagy az azt követő részre illeszkedik a (rész)minta.
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
|
|
Csoportosítás
Zárójelezéssel csoportosíthatunk termeket reguláris kifejezéseken belül. A zárójelen belül található rész önálló kifejezésként lesz alkalmazva.
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>>
|
|
A zárójelezéssel össze tudjuk gyűjteni a mintaillesztés eredményeit. A Ruby számon tartja a nyitó zárójeleket és mindegyikhez hozzárendel egy részeredmény változót, melyben az illeszkedő string darabja található a hozzátartozó csukó zárójelig. Ezeket a részeredményeket aztán felhasználhatjuk az illesztő mintánk későbbi részeiben vagy a Ruby programunkban. A mintán belül a \1 az első zárójelezett csoportra hivatkozik, a \2 a másodikra, és így tovább. A $1 , $2 , ... pedig ugyanezen hivatkozó változók Ruby programbeli megfelelői.
"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"
|
|
Ezt a képességet fel tudjuk használni sokféle ismétlődés illesztéséhez.
# illesztés duplázott betűkre
|
|
showRE('He said "Hello"', /(\w)\1/)
|
» He said "He<<ll>>o"
|
# illesztés duplázott részstringekre
|
|
showRE('Mississippi', /(\w+)\1/)
|
» M<<ississ>>ippi
|
|
A visszafele hivatkozások jók még határolójelek illesztésére is.
showRE('He said "Hello"', /(["']).*?\1/)
|
» He said <<"Hello">>
|
showRE("He said 'Hello'", /(["']).*?\1/)
|
» He said <<'Hello'>>
|
|
Minta alapú helyettesítések
Néha elegendő mintát keresni stringben. Ha egy barátod megbíz azzal a feladattal, hogy keress meg egy olyan szót, mely sorrenben a, b, c, d és e betűkből áll, megkeresheted a /a.*b.*c.*d.*e/ mintával, így meg fogod találni az "absconded" és "ambuscade" szavakat. Gyakran ennyi elég.
Habár, előfordul az, hogy meg kell változtatnod valamit, mely mintaillesztésen alapszik. Most térjünk vissza a dallistánkhoz. Akárki is hozta létre, a művészek nevét kisbetűkkel írta be. Amikor kiírnánk a neveket zenegépünk kijelzőjére, jobban mutatnának nagy kezdőbetűkkel. Akkor hogyan tudjuk kicserélni minden szó kezdőbetűjét nagyra?
A String#sub és String#gsub metódusok megkeresik a string eg részét az első paraméterben meghatározott minta szerint és kicserélik a másodikra. A String#sub az első csere után leáll, míg a String#gsub kicserél minden előfordulást. Mindkét metódus egy új stringet ad vissza tartalmazva a helyettesítéseket, viszont a String#sub! és String#gsub! változatuk önmagukon végzik el a helyettesítéseket.
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"
|
|
A második argumentum lehet akár string, akár kódblokk. Ha blokkot adunk meg, akkor a blokk értéke lesz a helyettesítő érték.
a = "the quick brown fox"
|
|
a.sub(/^./) { $&.upcase }
|
» "The quick brown fox"
|
a.gsub(/[aeiou]/) { $&.upcase }
|
» "thE qUIck brOwn fOx"
|
|
Szóval úgy tűnik, ez lesz a válasz művészeink nevének átalakítására. A minta, amely illeszkedik a szó első karakterére, a \b\w megkeresi a szavak határát, melyet betű követ. A gsub -bal kombinálva kész a hekk a művészek nevére.
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"
|
|
Backslash (\...) szekvenciák a helyettesítésben
Korábban már említettük, hogy a \1 , \2 , ... változók a mintában az n. illeszkedő csoportot képviselik. Ugyanezek a változók felhasználhatóak sub és gsub metódusok második paraméterében is.
"fred:smith".sub(/(\w+):(\w+)/, '\2, \1')
|
» "smith, fred"
|
"nercpyitno".gsub(/(.)(.)/, '\2\1')
|
» "encryption"
|
|
Vannak még további backslash szekvenciák, melyeket a fentihez hasonló módon használhatunk. Ilyen a \& (utolsó illeszkedés), \+ (utolsó illeszkedő csoport), \` (illeszkedés előtti string), \' (illeszkedés utáni string) és \\ (backslash literál). Az utóbbira akkor van szükség, ha \ jelet szeretnénk használni mintánkba.
Nyilvánvalóan ez az utasítás megpróbál minden backslash-t kicserélni kettőre. A programozó megkettőzte a backslash-ek számát a csereszövegben tudván azt, hogy azok \\ -re lesznek kicserélve. Azonban amikor a helyettesítés megtörténik, a reguláris kifejezés automata végrehajt még egy illesztést, melynek következtében \\ kicserélődik \ -re, így a végeredmény az lesz, hogy minden backslash kicserélődik egy másikra. A helyes megoldás tehát: gsub(/\\/, '\\\\\\\\') .
str = 'a\b\c'
|
» "a\b\c"
|
str.gsub(/\\/, '\\\\\\\\')
|
» "a\\b\\c"
|
|
Habár azt a tényt felhasználva, hogy a \& szekvencia kicserélődik az illesztett stringgel, így is írhatnánk:
str = 'a\b\c'
|
» "a\b\c"
|
str.gsub(/\\/, '\&\&')
|
» "a\\b\\c"
|
|
Ha a gsub blokk paraméteres formáját használjuk, a helyettesítendő string csak egyszer lesz analizálva és az eredmény épp az általunk kívánatos lesz.
str = 'a\b\c'
|
» "a\b\c"
|
str.gsub(/\\/) { '\\\\' }
|
» "a\\b\\c"
|
|
Végül álljon itt reguláris kifejezések kód blokkal kombinálásának egy csodálatos példája. Tekintsük a következő kódrészletet Wakou Aoyama által írt CGI könyvtár modulból. Ez vesz egy HTML-t tartalmazó string-et, majd átkonvertálja azt ASCII-re. Mivel a kód eredetileg japánoknak lett írva, az "n" módosító a reguláris kifejezés végén engedélyezi a több-bájtos karakterfeldolgozást. Továbbá jól illusztrálja a Ruby case kifejezését is.
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")
|
|
|
Ennek eredménye:
Objektum-orientált reguláris kifejezések
Be kell vallanunk, hogy míg ezek a fura változók igen hasznosak, nem túlságosan objektum-orientáltak és elég bizarrul néznek ki. És eddig nem azt mondtuk, hogy a Ruby-ban minden objektum? Akkor most mi a hiba?
Valójában semmi. Ez csak annyi, hogy amikor Matz a Ruby-t tervezte, megalkotott egy teljesen objektum-orientált reguláris kifejezés automatát, majd ezt Perl programozók számára ismerőssé alakította azzal, hogy a $-változókat becsomagolta. A felszín alatt azonban az objektumok és osztályok még mindig ott vannak. Most ássunk le oda és nézzünk körbe egy kicsit.
Már egy osztályt már megnéztünk: reguláris kifejezés literálok Regexp osztály példányát hozzák létre.
re = /cat/
|
|
re.type
|
» Regexp
|
|
A Regexp#match metódus reguláris kifejezést illeszt stringre. Ha ez nem sikerül, nil -t ad vissza. Sikeres illesztés esetén a MatchData osztály egy példányát kapjuk eredményül, melyen keresztül elérhetünk minden rendelkezésre álló információt. Mindent, amit $-változókból tudtunk kinyerni, azt most egy jópofa objektumba csomagolva kapjuk.
re = /(\d+):(\d+)/ # illesztés hh:mm formátumú időre
|
|
md = re.match("Time: 12:34am")
|
|
md.class
|
» MatchData
|
md[0] # == $&
|
» "12:34"
|
md[1] # == $1
|
» "12"
|
md[2] # == $2
|
» "34"
|
md.pre_match # == $`
|
» "Time: "
|
md.post_match # == $'
|
» "am"
|
|
Mivel az illesztési adatok egy objektumban vannak tárolva, eltehetjük kettő vagy több egyidejű illesztés eredményét is. Ezt $-változókkal nem tudjuk megtenni. A következő példában ugyanazt a Regexp objektumot illesztjük két különböző stringre. Mindegyik illesztés visszaadja a saját MatchData eredmény objektumát, melyeket visszaellenőrzünk megvizsgálva a két részmintát.
re = /(\d+):(\d+)/ # illesztés hh:mm formátumú időre
|
|
md1 = re.match("Time: 12:34am")
|
|
md2 = re.match("Time: 10:30pm")
|
|
md1[1, 2]
|
» ["12", "34"]
|
md2[1, 2]
|
» ["10", "30"]
|
|
Akkor hogy illeszkednek a képbe a $-változók? Nos, a Ruby minden egyes mintaillesztésnél elteszi a hivatkozást az utolsó eredmény objektumra (nil vagy MatchData lesz) egy szálra lokális $~ változóba. Az összes többi reguláris kifejezés változó aztán ebből származik majd. Habár nem gondoljuk a következő kódról, hogy hasznos lenne, azt demonstrálja, hogy a MatchData -beli $-változók tényleg elérhetők $~ -ból.
re = /(\d+):(\d+)/
|
|
md1 = re.match("Time: 12:34am")
|
|
md2 = re.match("Time: 10:30pm")
|
|
[ $1, $2 ] # utolsó sikeres illesztés
|
» ["10", "30"]
|
$~ = md1
|
|
[ $1, $2 ] # előző sikeres illesztés
|
» ["12", "34"]
|
|
Miután mindezt elmondtuk, meggyónunk. Andy és Dave a $-változókat használja és nem vesződik MatchData objektumokkal. Mindennapi használatra ezt kényelmesebbnek tartják. Néha egyszerűen nem tudunk segíteni nekik abban, hogy pragmatikusak legyenek.
|