A Ruby programozási nyelv

Típusok, típuskonstrukciók

1. Típusszerkezet

A Perl nyelvhez hasonlóan egyszerűen skalár típusúak a változóink. Standard osztályok formájában mindössze néhány speciális típus található meg. Külön a típusosztályokra nincsenek műveleteink, még az Object közös ősosztály sem definiál osztály szintű műveleteket. Műveleteket tehát csak objektumokkal végezhetünk. Új típusok képzése egyszerűen új osztályok létrehozásával oldható meg.

2. Egyszerű Típusok

2.1. Számok

A Ruby öt beépített osztályt tartalmaz a számok kezeléséhez és további három számokat reprezentáló osztály található a szabványos könyvtárakban:

Minden számot reprezentáló osztály a Numeric osztályból származik. Az egész számok kezeléséhez a Integer osztályt használhatjuk. Az Integer osztálynak két leszármazott osztálya van a Fixnum és a Bignum. A Ruby egyik érdekessége, hogy a Bignum osztály bármilyen nagy számot tartalmazhat. Nincs felső határa. Ha egy egész ábrázolása belefér egy natív gépi szó viszonyában, akkor automatikusan Fixnum lesz belőle, ha nem akkor Bignum. A két osztály közötti konverzió automatikus és teljesen átlátszó a programozó számára. Ha például két Fixnumot összeadunk és az eredmény nem fér bele egy bizonyos számú bites korlátba, akkor Bignum lesz belőle. Ez fordítva is igaz. Azaz, ha két Bignumot kivonunk egymásból és az eredmény már belefér a korlátba, akkor automatikusan Fixnumot kapunk. Ez azt is jelenti, hogy a Rubyban, más nyelvektől eltérően, nem fordulhat elő egész számok esetén túlcsordulás hiba. A korlát a natív gépi szóval áll viszonyban.

A valós számok kezeléséhez a Ruby a Float osztályt tartalmazza. Ez az aktuális platform, natív, lebegőpontos típusának segítségével reprezentálja a valós számokat.

A Complex osztály természetesen a komplex számokat takarja. A BigDecimal osztály tetszőleges pontosságú decimális számot ábrázol. A Rational pedig a racionális számokat, azaz egy Rational objektum két Integer objektumból áll elő.

irb(main):001:0> require 'rational'
=> true
irb(main):002:0> Rational(3, 3)
=> Rational(1, 1)
irb(main):003:0> 4.quo 2
=> Rational(2, 1)

A Rubyban minden Numeric típusú objektum megváltoztathatatlan (immutable), azaz nincs olyan módszer, ami módosítja az értékét. A 25 mindig 25 marad. Ha egy Numeric típusú objektum referenciáját átadjuk egy metódusnak, nem kell félnünk, hogy a metódus futása során megváltozik az értéke.

2.1.1. Integer literálok

Általában egy Integer literál a számjegyek és az aláhúzás karakter sorozatából áll. Az aláhúzás karakter nem állhat a sorozat legelején és a legvégén sem. Emellett két aláhúzás karakter nem állhat egymás mellet.

Példa:

435
0
1_112_100

Sokszor az aláhúzás karakter az ezreseket jelzi a forráskódban.

Mint más nyelvekben a Rubyban is a negatív számokat egy mínuszjellel képezzük.

-1_112
-2

Nem csak tízes számrendszerben adhatunk meg Integer literálokat. Ha a literál 0-val kezdődik és utána csak 8 alatti számjegyekből áll, akkor a Ruby interpretere oktális számként értelmezi. Ha a literál 0b-vel vagy 0B-vel kezdődik és utána csak 0-ák és 1-esek találhatóak, akkor az egy bináris. Ha literál 0x-szel vagy 0X-szel kezdődik és utána csak számjegyek illetve a-tól f-ig betűk szerepelnek, akkor az hexadecimálisként értelmeződik.

0377 # oktális 255
0b1111_1111 # bináris 255
0xFF # hexadecimális 255
2.1.2. Lebegőpontos literálok

A lebegőpontos literálok csak tízes számrendszerben adhatók meg. Hasonlóan az Integer literálokhoz kezdődhetnek mínusz jellel és aláhúzás jelet is tartalmazhatnak. Emellett szerepelhet benne pont karakter, ami a tizedesjegyet jelzi és exponens is. Az exponens 'e' vagy 'E' karakterrel kezdődhet, utána pedig egy egész szám áll (természetesen lehet negatív is).

Fontos megjegyezni, hogy a Ruby először Integer-ként próbálja először értelmezni a szám literálokat. Ezért ahhoz, hogy megadjunk egy Float literált, mindenképpen kell írni legalább egy pont karaktert vagy egy exponenst. Ez például, akkor fontos, ha két számot akarunk elosztani egymással. A Rubyban, ha egy egész számot elosztunk egy másik egésszel, akkor egészet kapunk eredményül. Figyeljük meg a következő részletet:

irb(main):001:0> 1/2
=> 0
irb(main):002:0> 1.0/2
=> 0.5
irb(main):003:0> 1e0/2
=> 0.5
irb(main):004:0> 1/2e0
=> 0.5

A Rubyban a pont karakter elé mindenképpen írni kell egy számjegyet. A következő literált a Ruby interpreter-je nem tudja értelmezni:

.1

2.2. Stringek

A Rubyban a stringek kezeléséhez a String osztályt használhatjuk. A stringek ellentétben a numerikus típusokkal megváltoztathatók (mutable). A Ruby interpretere nem használja ugyanazt az objektumot két azonos string literálra. Mindig új objektumot készít.

A Ruby 1.8-as verziójában még nem volt Unicode kezelés, ami azért is meglepő, mert a nyelvet Japánban fejlesztették ki. Szerencsére az 1.9-es verzióban az már használhatunk több byte-os kódolásokat is, mint például a Unicode.

A Rubyban igen sokféle módon lehet string literálokat írni a forráskódba.

2.2.1. Aposztróffal határolt string literálok

A legegyszerűbb string literálok az aposztróffal határolt string literálok. Az aposztrófok közötti szöveg lesz a string értéke.

'ablak'

Ha aposztróf karaktert akarunk beilleszteni a stringbe, akkor a backslash karakterrel kell prefixálnunk.

'-Mennyi 12 osztva 4-gyel? -Nem t\'om.' # -Mennyi 12 osztva 4-gyel? -Nem t'om.

A backslash karaktert szintén lehet backslash-el prefixálni, de nem kötelező.

irb(main):001:0> 'a\a'
=> "a\\a"
irb(main):002:0> 'a\\a'
=> "a\\a"

Az aposztróffal határolt string literálokat nem kell a sor végén lezárni. Nyugodtan lehet folytatni több soron keresztül. Ilyenkor a string az új sor karaktereket is tartalmazni fogja.

2.2.2. Idézőjellel határolt string literálok

Az idézőjellel határolt string literálok sokkal több lehetőséget nyújtanak, mint az aposztróffal határoltak. Több blackslash escape szekvenciát írhatunk egy ilyen literálba. Az újsorokat a "\n", a tabulátort a "\t" karaktersorozat jelzi. Ennél a literál típusnál már kötelező a backslash karaktert egy másik backslash karakterrel prefixálni. Az 1.9-es verzió fölött már lehetőség van a Unicode karakterek használatára. Ezeket a "\u"-val kell prefixálni.

"\u20ac" # => "€"

A idézőjellel határolt literálok egyik leghasznosabb lehetősége, a behelyettesítés. Ha egy ilyen literál tartalmazza következő karaktersorozatot: "#{x}", akkor az interpreter az x-et mint kifejezés kiértékeli is behelyettesíti a stringbe. Az x helyére tetszőleges Ruby kifejezést írhatunk.

irb(main):001:0> x=3
=> 3
irb(main):002:0> "hat=#{x*2}"
=> "hat=6"

Ha egy globális, példány vagy osztály változót, akarunk behelyettesíteni, akkor a kapcsos zárójeleket el is hagyhatjuk.

irb(main):001:0> $x = 3
=> 3
irb(main):002:0> "#$x + 1"
=> "3 + 1"
2.2.3. Tetszőleges karakterrel határolt string literálok

Ha olyan szöveggel dolgozunk, ami sok aposztrófot vagy idézőjelet tartalmaz, sokszor kényelmetlen az előzőekben felsorolt string literálok használata. A Rubyban lehetőség van tetszőleges karakterrel határolni a stringeket. Az ilyen literálokat a %q vagy a %Q karaktersorozattal kell kezdeni. A következő karakter fogja a string két oldalról meghatározni. Lássunk egy pár példát:

%q_Használhatunk aposztrófot? Nem t'om._
%Q|"Ki a'?" - kérdezte Elemér|

Ha egy stringet a %q karaktersorozattal kezdünk, akkor az aposztróffal határolt string literáloknál megismert szabályok érvényesek. Ha %Q karakterekkel kezdünk, akkor pedig a idézőjel határolt stringliteráloknál megismert szabályok lépnek érvénybe. Mindkét esetben a kiválasztott határoló karaktert escape-elni kell a backslash karakterrel.

%q_Most egy aláhúzás jön:\_._

Vannak páros határoló karakterek. Ha például a %q után nyitó zárójelet írunk, akkor nem a nyitó zárójellel ér véget a string, hanem a csukó párjával.

%q(Zárójeles rész)

A páros határoló jelek a következők:

A páros határoló karakterek, csak a megfelelő irányban működnek.

%q)Zárójeles rész( # Hibás
%q)Zárójeles rész) # Így jó

A páros határoló karaktereknél, nem feltétlenül kell escape-elni a határolókat. Ha megfelelő a zárójelezés, akkor azt a Ruby interpreterje elfogadja.

%q<<htlm></htlm>>
2.2.4. `Here docomunents`

Ha egy viszonylag hosszú szöveget kell beilleszteni a kódba, amiben sok fajta karakter szerepel, akkor még a tetszőleges karakterrel határolt string literáloknál is kényelmetlen lehet a határolók escape-elése. Ezért a Rubyban van még egy lehetőség a stringek nyitására és lezárására. Ez a here documents.

A `here documents` << vagy <<- karaktersorozattal kezdődik. Az utána közvetlenül jön a határoló karaktersorozat. A stringnek akkor van vége, ha egy sorban önállóan áll a határoló szöveg. A here documents-re ugyanazok a szabályok vonatkoznak, mint az idézőjellel határolt string literálokra. Ezért akár behelyettesítést is használhatunk a szövegben.

dokumentum = <<HATAROLO Ez egy hosszú szöveg. Itt egy behelyettesítés: #{x}. HATAROLO
2.2.5. Backquote-parancs végrehajtás

Nagyon jól használható lehetőség, főleg linux platformon, a backqoute karakterekkel határolt string. Ha az interpreter egy ilyen szöveget talál, akkor azt, mint egy shell szkriptet végrehajtja és az eredmény behelyettesíti.

a = `ls`

Az előző kódrészletben az aktuális könyvtárban elvégzett ls parancs eredménye belekerül az 'a' változóba.

A Backquote karakterek helyett használhatjuk a %x karaktersorozatot is. A %x után meg kell adni egy határoló karaktert. Mind a backquote karakteres, mind a %x formában a stringre a idézőjellel határolt literáloknál megismert szabályok érvényesek. Itt is használhatjuk a behelyettesítést.

if windows   listcmd = 'dir' else   listcmd = 'ls' end listing = %q/#{listcmd}/
2.2.6. Karakter literál

A Rubyban lehetőség van karakter literálokat is írni. Érdekesség, hogy az általánostól eltérően nem kell jelölni, hogy hol van a vége a karakternek, hiszen egy karakter hossza mindig egy karakter. A karakter literált egy kérdőjellel segítségével kell leírni.

a_karakter = ?a kerdojel_karakter = ??
2.2.7. String operátorok

A String osztály sok hasznos operátort tartalmaz a stringek kezeléséhez. A + operátor két karaktersorozatot konkatenál. A jobbkifejezés nem konvertálódik stringgé, mint például a Javaban. Ezt a programozónak kell megtenni.

nagy_ember = "Cézár" udvozlet = "Ave " + nagy_ember egyenlet = "2 + 1 = " + 3.to_s

A behelyettesítésnél ez a konverzió megtörténik, ezért érdemesebb azt használni.

A << operátor szintén összefűz két stringet, de az a balkifejezést megváltoztatja. Itt is figyelni kell arra, hogy a jobbkifejezés nem konvertálódik implicit stringgé.

nagy_ember = "Cézár" udvozlet = "Ave " udvozlet << nagy_ember puts udvozlet # => "Ave Cézár"

A * operátor egy stringen és egy egész számon működik. A stringet annyiszor fűzi össze, amennyi a megadott szám.

nagyon_dolgoznank = "Dolgoznánk!" * 3 # => "Dolgoznánk!Dolgoznánk!Dolgoznánk!"

A Rubyban meg vannak valósítva az általában elterjedt string hasonlító operátorok is. Az == és az != hasonlóan működik mint más programnyelvekben. Az első egyezőséget vizsgál, a második pedig nem egyezőséget. Ezen kívül van négy hasonlító operátor van: <, <=, > és >= . Ezek az ábécé sorrend alapján - amit egyébként a karakterkódok értéke alapján számolnak - döntik el, hogy melyik string a nagyobb és melyik a kisebb. Ha az egyik string tartalmazza a másikat, akkor a rövidebb string kisebb mint a másik. Mint már említettük a sorrend alapja a karakterek kódja. Ezért sajnos ezek az operátorok nem használhatóak a magyar ábécé szerinti sorrend eldöntésére. Persze, ha muszáj, akkor felül lehet definiálni őket.

2.2.8. Karakterek és részstringek címzése egy stringben

A Rubyban is, mint például a C-ben a [] operátor segítségével lehet elérni a string egyes karaktereit. Ami érdekesség lehet, hogy nem csak nem-negatív számokat írhatunk a szögletes zárójelek közé, hanem negatívakat is. Ilyenkor a Ruby a string végétől számítva adja vissza a kívánt karaktert. Egy string utolsó karakterét, nem csak a string[string.length -1] utasítással lehet elérni, hanem a jóval egyszerűbb és elegánsabb s[-1] utasítással is.

Az indexek túllépése esetén - akár negatív, akár pozitív irányba is történik - nem váltódik ki kivétel. Egyszerűen egy nil-t fog visszaadni a kifejezés.

Lehetőség van a hivatkozott karaktert megváltoztatására is, azonban nem csak karakterre változtathatunk egy karaktert, hanem egy stringre is.

penzem = "kilenc forint" penzem[2] = "lencszazkilencvenkil" # penzem = "kilencszazkilencvenkilenc forint"

Ha egy rész stringet szeretnénk kinyeri a változónkból, akkor a [] jelek közé két számot is lehet írni, vesszővel elválasztva. Az első szám, ami lehet negatív is, annak a karakternek a pozícióját jelöli, ahonnan a részt stringet elkezdi a Ruby kiolvasni. A második szám pedig azt határozza meg, hogy hány karakter hosszú legyen a részlet. Ha túl hosszút adunk meg, akkor csak annyit olvasódik ki, amennyi van. Kivétel nem keletkezik. Ha második szám nulla, akkor minden esetben üres stringet kapunk.

s = "kilenc forint" s[1,2] # "il" s[-3,2] # "in" és nem "ni"

A szögletes zárójelek között megadhatunk egy Range objektumot is. Ilyenkor a Range által meghatározott intervallumba eső indexek jelölik ki a rész-stringet.

s = "kilenc forint" s[1..4] # "ilen" s[1...4] # "ile" s[-3..-2] # "in"

Még egy string indexelési lehetőség van a Rubyban. Indexelhetünk stringet stringgel. Az eredmény az indexnek használt string első előfordulása lesz az eredeti stringben. Ha egyszer sem fordul elő, akkor a kifejezés értéke nil lesz.

s = "kilenc forint" s["ilen"] # "ilen" s["valami"] # nil

2.3. Mutató típusok

Pointer osztály nincs a nyelvben. Persze tudjuk, hogy az objektumok valójában dinamikusan lefoglalt memóriaterületekre kerülnek, még ha a programozó nem is jelölhet meg pointerrel egy ilyen területet. A feleslegessé váló memóriaterületek felszabadítása természetesen automatikus szemétgyűjtéssel történik. Mivel pointereket nem használhatunk, különösebben a szemétgyűjtés folyamatába sem tudunk beleavatkozni.

2.4. Szimbólumok

Egy szimbólum gyakorlatilag egy névvel ellátott azonosítót jelöl. Jelölésük: :symbolname vagy :"tobb szavas szimbolum". Az értékük értelemszerűen nem változtatható meg. Abban jelentkezik a hasznuk, hogy azonos nevű szimbólumok mindig azonos objektumot jelölnek, szemben például a stringekkel. Alkalmazhatóak minden olyan esetben, amikor valami állandó dolgot akarunk átadni, pl. hash-ek kulcsaként, enumok értékeiként, stb. Ezen kívűl kódbeli szövegek azonosítására is használható (metódusnevek, változók), pl. láthatóság megváltoztatásakor.

:smb.object_id == :smb.object_id # true "smb".object_id == "smb".object_id # false

3. Konténerek, típuskonstrukciók

A nyelvben alapvetően három féle konténer található:

A nyelveben alapértelmezés szerint minden változó skalár típusú. Ez azt jelenti, hogy a változó egy objektumra mutató referenciát tárol. A változóknak ennél fogva nincs típusuk, ellentétben az általuk hivatkozott objektumokkal. A dereferálás "bele van égetve" a nyelvbe, tehát ennek elvégzésével külön nem kell foglalkozni. Az alábbi példában egy String illetve egy Fixnum objektumot tárolunk el egy skalár típusú változóban

mystring = "Some string" mynumber = 13

A tömb konstrukció az Array standard osztály formájában adott. Legegyszerubben a [1, 2, 3, 4] formában lehet egy tömböt létrehozni, ami az Array egy objektuma lesz. A tömbök egészekkel indexeltek és az indexelés nullától indul. Indexelhetoek a tömbök hátulról is, azaz a -1 indexu elem a tömb utolsó indexét jelenti, a -2 az utolsó elottit stb. Tömb eleme tetszőleges objektum lehet. A tömb elemei menet közben törölhetoek és új elemek is felvehetoek. Tulajdonképpen az Array osztályban a vermeknél megszokott push és pop muvelet mellett más, a tömbök esetén szokatlan, de azért hasznos és kényelmes muveletek (pl. shift, include?, sort, uniq, stb.) is implementálásra kerültek. Tömbkonstanst létrehozhatunk úgy, hogy egy konstans formájú, azaz CSUPA_NAGYBETUVEL irt azonosítóhoz rendelünk egy tömböt, ill. a freeze metódussal megtilthatjuk egy tömb további módosítását. Tömbelem lehet tömb is, így készíthetünk többdimenziós tömböket. Az indexelés a szögletes zárójelekkel történik. Ha a zárójelek között egy indexintervallumot adunk meg, az egy tömbszelet értéku kifejezés. Értékadást a tömb elemeire, tömbszeletekre és az egész tömbre is elvégezhetünk.

tomb = [1, 2, 3, 4, 5, 6] tomb[2] = 13 #[1, 2, 13, 4, 5, 6] tomb[2..4] = [9, 8, 7, 6] #[1, 2, 9, 8, 7, 6, 6]

A hash-ek, azaz az asszociatív tömbök olyan adatszerkezetek, melyek tömbként viselkednek, indexelésükre azonban tetszőleges típusú objektum használható. Egy hash elemeinek felsorolásakor azok a létrejöttük sorrendjében lesznek visszaadva. Ha egy nem létező elemre hivatkozunk, akkor alapértelmezett esetben a nil értéket kapjuk vissza. Egy egyszerű példa:

$hash = { 'key1' => 'value1', 'key2' => 'value2' }

Rekordokat a Struct osztály példányosításával készíthetünk. Van néhány kikényszerítheto típuskonverzió. Pl. az Array(x), Float(x), Integer(x) vagy String(x) kifejezések az x megfelelo típusúvá alakítását jelölik. Rubyban a toString helyett inkább a to_a tömb alakra konvertáló metódus a jellemzo, melybol az Array osztályban implementált join nevu metódussal tudunk stringet kreálni. A stringben a tömb mezoi a join paramétereként megadott szeparátorral lesznek elválasztva, ami alapértelmezésben a $, speciális változó tartalma. Rubyban a Perl-hez hasonló változók, mint amilyen a $_ is használhatóak. A String osztálynak létezik egy to_f és egy to_i Float-tá, illetve Integer-ré alakító metódusa.

4. Típuskonverziók

A típuskonverziók is osztályokkal vannak megvalósítva.

5. Változók, konstansok

A Ruby a Perl nyelvhez hasonlóan nem szigorúan típusos. A változók tetszőleges értéket felvehetnek, sőt deklarálni sem kell őket. Pontosabban az első rájuk vonatkozó értékadás tekinthető a deklarációjuknak és az ilyen módon sem deklarált változók értékére való hivatkozás nem megengedett. A programunk változóinak láthatóságát az azonosítójuk formája határozza meg. Az osztályok és objektumok attribútumai kívülről nem láthatók. Mivel a deklaráció nem más, mint egy eddig ismeretlen nevű változóra vonatkozó első értékadás, természetesen a Ruby-programban lehet blokkon belül is változót deklarálni, és ezek a változók az elvárásoknak megfelelően csak a blokkon belül lesznek deklarálva.

6. Kifejezések, operátorok

Rubyban minden kifejezés, így az értékadás is az.

x = y x op= y obj.x = y

Az op lehet +, -, *, /, %, <<, >>, stb.... Az obj.x = y alakú értékadás látszólag egy objektum egy attribútumához való közvetlen hozzáférés, de valójában ez egy setter metódus hívása.

7. Reguláris kifejezések

Egy reguláris kifejezés egy olyan, bizonyos szintaktikai szabályok szerint leírt string, amivel meghatározható stringek egy halmaza. Általában mintaillesztésre használatos. A Ruby beépített megoldásként biztosítja a reguláris kifejezések kezelését, mely igen nagy mértékben megkönnyíti a mintaillesztést, és a string-ek kezelését.

A reguláris kifejezések Regexp típusú objektumok. Létrehozásuk kétféleképpen történhet:

a = Regexp.new('^\s*[a-z]') b = /^\s*[a-z]/ c = %r{^\s*[a-z]}

Miután létrehoztunk egy reguláris kifejezés objektumot, lehetőség nyílik egy string-en való mintaillesztésre a Regexp osztály match(string) metódusát felhasználva, vagy közvetlenül az erre a célra kitalált operátorok egyikével:

A fenti operátorok String és Regexp objektumokon is definiáltak. Azonban legalább az egyik operandusnak reguláris kifejezésnek kell lennie. (Régebbi verziók esetén lehetőség volt arra is, hogy mind a kettő operandus String objektum legyen. Ebben az esetben a második operandus automatikusan Regexp-re konvertálódott.)

name = "Miles Davis" name =~ /a/ # => 7 name =~ /z/ # => nil /a/ =~ name # => 7

Az operátor a mintaillesztés után azt a karakter pozíciót adja vissza, ahol az első illeszkedés történt. A mintaillesztés mellékhatással is jár, több globális változót is beállít ilyenkor a Ruby. A $& változóba kerül az a szövegrészlet, ami illeszkedett a reguláris kifejezésre, a $` változó tárolja el az illeszkedés előtti rész-string-et, míg a $' változó tárolja el az illeszkedés utáni szövegrészletet. Ezt kihasználva könnyen írható olyan metódus, ami kiemeli egy string azon részét, ami illeszkedik egy reguláris kifejezésre.

def highlight_match(str, regexp) if str =~ regexp "#$`<<#$&>>#$'" else "no match" end end highlight_match('Test string', /st/) # => Te<<st>> string highlight_match('Miles Davis', /a/) # => Miles D<<a>>vis highlight_match('Miles Davis', /bb/) # => no match

A fenti változókon kívül továbbiak is beállításra kerülnek egy-egy mintaillesztés során. A $~ változó egy MatchData objektumot tartalmaz, ami minden elérhető információt tartalmaz egy mintaillesztésről. Ilyen információk például, hogy hány illeszkedés volt az adott stringen belül, illetve, hogy mik voltak ezek.

7.1. Minták

Minden egyes reguláris kifejezés egy mintát tartalmaz, aminek segítségével történik a mintaillesztés egy String objektumra. Egy-egy mintában minden egyes karakter önmagának felel meg, kivétel a következő néhány jel, melyek speciális jelentéssel bírnak: ., |, (, ), [, ], {, }, +, \, ~, $, *, ? .

highlight_match('!%!@@&%--++44', /&%/) # => "!%!@@<<&%>>--++44"

Ha a fent felsorolt karakterek egyikére szeretnénk illeszteni, akkor természetesen erre is van lehetőség, ekkor escape-lni kell. Ezt úgy tehetjük meg, hogy egy '\' karaktert írunk a kívánt speciális elem elé.

highlight_match('4 + 5 = 2', /\+/) # => "4 <<+>> 5 = 2" highlight_match('Do you want to quit? (yes | no)' , /\?/) # => "Do you want to quit<<?>> (yes | no)" highlight_match('Do you want to quit? (yes | no)' , /\|/) # => "Do you want to quit? (yes <<|>> no)"

Egy reguláris kifejezés is tartalmazhat behelyettesítendő elemet, akárcsak egy String. Ezt a megszokott #{kifejezés} szintaxissal érhetjük el.

7.2. Horgonyok

Alapértelmezett esetben egy reguláris kifejezés a mintaillesztés során az első illeszkedést próbálja megtalálni. Előfordulhat azonban olyan eset is, amikor konkrétan egy String elejére, vagy végére kell illeszteni egy kifejezést. Ennek a megvalósítására használhatóak a különböző horgonyok.

A ^ jellel kezdődő minták egy sor elejére illesztenek, míg a $ jelre végződő minták egy sor végére próbálnak illeszteni: például a /^Ness/ minta csak akkor illeszkedik, ha a Ness a vizsgált sor elején van, azaz a 'Loch Ness' szövegre nem, viszont 'Nessie' szövegre illeszkedik. Egyéb horgonyok is használhatók. A \A szekvencia egy string elejére illeszt, míg a \z és \Z egy string végére. (Pontosabban a \Z egy string végére illeszt, kivéve ha az \n-re végződik, mert akkor a \n-t figyelmen kívül hagyja.)

highlight_match("this is\nthe sample, /^the/) # => "this is\n<<the>> sample" highlight_match("this is\nthe sample, /is$/) # => "this <<is>>\nthe sample" highlight_match("this is\nthe sample, /\Zis/) # => "no match" highlight_match("this is\nthe sample, /\Ath/) # => "<<th>>is is\nthe sample" highlight_match("this is\nthe sample, /\Athe/) # => "no match"

Hasonlóan működnek a \b és \B módosítók is. Az előbbi olyan szavakra illeszkedik ahol a minta egy szó eleje, míg az utóbbi olyan esetekben illeszt, ha a mintát legalább egy karakter közvetlenül megelőzi. Másképpen mondva az első prefix, a második infix módon illeszt.

highlight_match('Hello eleanor',/\bel/) # => "Hello <<el>>eanor" highlight_match('Hello eleanor',/\Bel/) # => "H<<el>>lo eleanor"

7.3. Karakter osztályok

A szögletes zárójelek között felsorolt karakterek halmazát nevezik karakter osztálynak. Egy mintaillesztés a karakter osztály minden egyes karakterére illeszt. Például [aáeéiíoóöőuúüű] illeszkedik az összes magyar nyelvben megtalálható magánhangzóra, a [.,:;!?] illeszkedik a különböző írásjelekre és így tovább. Az egyes korábban felsorolt speciális jelek, ha karakter osztályok részét képezik, akkor elveszítik a különleges jelentésüket. Azonban a megszokott string behelyettesítés megtörténik, azaz \b egy backspace karaktert jelöl, ahogy a \n pedig egy új sort. Ezeken kívül több rövidítés is használható, melyek táblázatos formában megtalálhatóak alul.

highlight_match('Price $12',/[aeiou]/) # => "Pr<<i>>ce $12" highlight_match('Price $12',/[\s]/) # => "Price<< >>$12" highlight_match('Price $12',/[[:digit:]]/) # => "Price $<<1>>2" highlight_match('Price $12',/[[:space:]]/) # => "Price<< >>$12" highlight_match('Price $12',/[[:punct:]aeiou]/) # => "Pr<<i>>ce $12"

Karakterosztályok leírásánál is léteznek speciális jelentésű sorozatok. A c1-c2 jelöléssel leírt sorozat szögletes zárójelek között az összes c1 és c2 közötti karaktert jelöli, beleértve az intervallum széleit is.

a = 'see [Design Patterns-page 123]' highlight_match(a, /[A-F]/) # => see [<<D>>esign Patterns-page 123] highlight_match(a, /[A-Fa-f]/) # => s<<e>>e [Design Patterns-page 123] highlight_match(a, /[0-9]/) # => see [Design Patterns-page <<1>>23] highlight_match(a, /[0-9][0-9]/) # => see [Design Patterns-page <<12>>3]

Ha a ']' vagy '-' karakterekre szeretnénk illeszteni a karakter osztállyal, akkor a minta elején kell megjelenniük. Escape-elni is lehet őket, ezáltal a karakter osztály tetszőleges részén is megjelenhetnek. Ha a karakter osztály nyitó zárójelét a '^' karakter követi, akkor az negálást jelent. Azaz a [^0-9] osztály mindenre illeszkedik, ami NEM számjegy.

a = 'see [Design Patterns-page 123]' highlight_match(a, /[]]/) # => see [Design Patterns-page 123<<]>> highlight_match(a, /[-]/) # => see [Design Patterns<<->>page 123] highlight_match(a, /[^a-z]/) # => see<< >>[Design Patterns-page 123] highlight_match(a, /[^a-z\s]/) # => see <<[>>Design Patterns-page 123]

Több olyan karakter osztály is létezik, amik használata annyira gyakori, hogy a Ruby külön rövidítést is bevezetett a használatukra. Ezen rövidítések egyaránt szerepelhetnek a különböző karakterosztályokon belül, és az egyéb reguláris kifejezések törzsében is. A részletes leírása a rövidítéseknek megtalálható a fejezet végén szereplő táblázatban.

highlight_match("This is a test message with 8 words", /\s/) # => This<< >>is a test message with 8 words highlight_match("This is a test message with 8 words", /\d/) # => This is a test message with <<8>> words

A karakter osztályokon kívül használt '.' egy tetszőleges új sor karaktertől eltérő karaktert jelöl.

a = 'It costs $12.' highlight_match(a, /c.s/) # => It <<cos>>ts $12. highlight_match(a, /./) # => <<I>>t costs $12. highlight_match(a, /\./) # => It costs $12<<.>>
A karakter osztályoknál használatos rövidítések
Rövidítés '[' ']' közötti jelölés Jelentés
\d [0-9] Számjegyek
\D [^0-9] Bármilyen karakter számjegyek kivételével
\s [\s\t\r\n\f] Whitespace karakterek
\S [^\s\t\r\n\f] Bármilyen karakter whitespace karakterek kivételével
\w [A-Za-z0-9_] Egy szóban használható karakterek
\W [^A-Za-z0-9_] Minden karakter amire \w nem illeszkedik
POSIX karakter osztályok
[:alnum:] Alfanumerikus karakterek
[:alpha:] Kis vagy nagybetűs karakterek
[:blank:] Space és tab
[:cntrl:] Vezérlőkarakterek (Pl. line feed, carriage return)
[:digit:] Számok
[:graph:] Nyomtatható, azaz látható karakterek (\x20-\x7E) space kivételével
[:lower:] Kisbetűs karakterek
[:print:] Nyomtatható karakterek space-t is beleértve
[:punct:] Központozásra használt karakterek (pl.: !,",#,$,%,&,',(,))
[:space:] Whitespace karakterek
[:upper:] Nagybetűs karakterek
[:xdigit:] Hexadecimális számok (0-9, a-f, A-F)

7.4. Ismétlődés

Reguláris kifejezések írásánál gyakran megesik, hogy egy-egy karakter, vagy sorozat előfordulásánál azt szeretnénk, hogy a minta egymás után többször jelenjen meg. Ilyen eset lehet például, amikor egy szövegben sok egymás melletti space-t egy darabra szeretnénk cserélni. Ilyen esetekben különböző módosító karakterek segítségével kifejezhetünk ismétlődést.

Legyen r a vizsgálandó reguláris kifejezés a mintában. Ekkkor az

Ezek az ismétlődést definiáló konstrukciók magas precedenciával rendelkeznek, csak a közvetlenül megelőző kifejezésre illeszkednek. Pl.: /ab+/ olyan szövegre illeszkedik, ahol egy a-t legalább egy b követ, azaz nem az ab szekvenciákra. A *-al megadott konstrukciókkal is vigyázni kell, mivel egy /a*/ kifejezés mindenre illeszkedni fog.

Ezek a minták mohók, mivel alapértelmezetten a legnagyobb rész-string-re próbálnak illeszkedni. Ezt a viselkedést természetesen meg lehet változtatni úgy, hogy a minimumra illeszkedjenek. Ezt a vezérlőkarakter után írt '?'-el lehet elérni.

a = "The moon is made of cheese" highlight_match(a, /\w+/) # => <<The>> moon is made of cheese highlight_match(a, /\s.*\s/) # => The<< moon is made of >>cheese highlight_match(a, /\s.*?\s/) # => The<< moon >>is made of cheese highlight_match(a, /[aeiou]{2,99}/) # => The m<<oo>>n is made of cheese highlight_match(a, /mo?o/) # => The <<moo>>n is made of cheese

7.5. Váltakozás

Korábban már látni lehetett, hogy a '|' karakter speciális jelentéssel bír, hiszen egyes mintákban escapelni kellett. Egy '|' karakterrel megadott minta vagy a '|' karaktert megelőző reguláris kifejezésre, vagy az őt követő kifejezésre illeszkedik.

a = "red ball blue sky" highlight_match(a, /d|e/) # => r<<e>>d ball blue sky highlight_match(a, /al|lu/) # => red b<<al>>l blue sky highlight_match(a, /red ball|angry sky/) # => <<red ball>> blue sky

A '|' karakter használatánál arra kell figyelni, hogy míg az ismétlődésnél szereplő jelek magas precedenciával rendelkeztek, addig a '|' alacsonnyal rendelkezik. Ez a fenti példán jól látszik, hiszen a /red ball|angry sky/ a red ball vagy angry sky szövegre illeszkedik és nem a red ball sky vagy red angry sky-ra. Ha az utóbbi viselkedést szeretnénk elérni, ahhoz felül kell írni a precedenciát csoportosítással.

7.6. Csoportosítás

Az egyes reguláris kifejezésekben lehetőség van zárójelekkel egyes tagokat csoportosítani. Minden, ami zárójelek között szerepel egy reguláris kifejezésként működik.

highlight_match('banana', /an*/) # => b<<an>>ana highlight_match('banana', /(an)*/) # => <<>>banana highlight_match('banana', /(an)+/) # => b<<anan>>a a = "red ball blue sky" highlight_match(a, /blue|red/) # => <<red>> ball blue sky highlight_match(a, /(blue|red) \w+/) # => <<red ball>> blue sky highlight_match(a, /(red|blue) \w+/) # => <<red ball>> blue sky highlight_match(a, /red|blue \w+/) # => <<red>> ball blue sky highlight_match(a, /red (ball|angry) sky \w+/) # => no match a = "the red angry sky" highlight_match(a, /red (ball|angry) sky \w+/) # => the <<red angry sky>>

A zárójelezés az előbbi működésen felül gyűjti a mintaillesztés eredményét. A Ruby számolja a zárójelezett kifejezéseket, és mindegyikhez eltárolja az illeszkedésnek azt a részét, ami a zárójelezett kifejezést magába foglalja. Ezt a részleges illesztést a minta további részében, vagy a programban fel lehet használni. A mintán belül a \1-el lehet hivatkozni az első csoportra, \2-vel a másodikra és így tovább. A programon belül speciális globális változókon keresztül lehet ezt megrenni: $1,$2,... szolgálnak erre.

"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"

Az a tulajdonság, hogy egy korábbi rész illeszkedés újra felhasználható lehetőség biztosít arra, hogy bizonyos ismétlődéseket keressünk a szövegben.

# duplikált betűk keresése highlight_match('He said "Hello"', /(\w)\1/) # => He said "He<<ll>>o" # duplikált részstring-ek keresése highlight_match('Mississippi"', /(\w+)\1/) # => M<<ississ>>ippi

7.7. Minta alapú csere

Néha egy minta megkeresése egy szövegben elegendőnek bizonyul. Azonban általában a mintaillesztés alapján sokszor változtatni szeretnénk a szövegen. Ilyen eset lehet például, ha egy szöveg minden egyes szavában a kezdőbetűt nagyra akarjuk változtatni.

A String osztály sub és gsub metódusai az első paraméterben megadott kifejezéssel keresnek, és az illeszkedő részstringet a második paraméterben megadott szöveggel helyettesítik. A sub metódus egy cserét hajt végre, míg a gsub metódus az összes előfordulás esetén végrehajtja a cserét. Mind a kettő függvényre igaz, hogy visszaad egy másolatot a változásokat tartalmazó Stringről. A megszokott módon léteznek sub! és gsub! függvények is, amik az eredeti szöveget is megváltoztatják.

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"

Mind a kettő függvényre igaz, hogy a második paraméter vagy egy String vagy egy blokk lehet. Blokk használata esetén, a blokk megkapja az illeszkedő részstringet, és a blokk értéke visszahelyettesítődik az eredeti szövegbe.

a = "the quick brown fox" a.sub(/^./) {|match| match.upcase} # => "The quick brown fox" a.gsub(/[aeiou]/) {|vowel| vowel.upcase } # => "thE qUIck brOwn fOx"

Ezzel a módszerrel el lehet érni azt, hogy nagy kezdőbetűssé konvertáljunk kapott neveket, ha szükséges.

def mixed_case(name) name.downcase.gsub(/\b\w/) { |first| first.upcase } end mixed_case("fats waller") # => "Fats Waller" mixed_case("LOUIS ARMSTRONG") # => Louis Armstrong" mixed_case("sTrEngth In NUMberS" # => Strength In Numbers"

7.7. Backslash szekvenciák alkalmazása cserékben

Korábban szó esett róla, hogy a \1, \2 szekvenciák egy mintában az n-edik csoportot jelzik. Ezek a szekvenciák ugyanúgy használhatóak a sub és gsub függvények második paraméterében is.

"fred:smith".sub(/(\w+):(\w+)/, '\2, \1') # => "smith, fred" "nercpyitno.gsub(/(.)(.)/, '\2\1') # => "encryption"

További szekvenciák is használhatóak a csere során:

Amikor egy tényleges '\' karakterre szeretnénk illeszteni, akkor zavarossá válhatnak a dolgok, a sok escape-elendő karakter miatt:

str.gsub(/\\/, '\\\\')

A fenti kód egy szövegben minden egyes '\' karaktert '\\'-re cserél. Ugyanezt a hatást el lehet érni a \& szekvencia felhasználásával, ami az utolsó illeszkedést tartalmazza.

str = 'a\b\c' # => "a\b\c" str.gsub(/\\/, '\&\&') # => "a\\b\\c"

A reguláris kifejezések alapján történő cserére jó példa található a CGI könyvtár modulban. A kódot Wakou Aoyama írta. A minta egy HTML escape szekvenciákat tartalmazó string-et vár, és normális ASCII karakterekké konvertálja azt.

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 Integer($1).chr end } str end puts unescapeHTML("1<2 && 4>3") # => 1<2 && 4>3 puts unescapeHTML(""A" = A = A") # => "A" = A = A

7.8. Objektum-orientált reguláris kifejezések

A korábban bemutatott változók használata nagyon kényelmes tud lenni, azonban nem túlságosan objektum-orientált. A Ruby-ban azonban minden objektum. A valóságban a reguláris kifejezés kezelő rendszer teljesen obejktum-orientált, azonban, hogy a Perl programozók számára is ismert és kényelmes módszerek megjelenjenek, a nyelv készítése során a $-változókat "beburkolták" a rendszer legfelső szintjén. Az objektumok és osztályok léteznek a felszín alatt.

Korábban már volt szó egy osztályról: egy reguláris kifejezés literál egy Regexp objektum.

/cat/.class # => Regexp

A Regexp osztály match metódusa illeszt egy kifejezést egy stringre. Ha nem sikerül akkor nil-t ad vissza. Siker esetén visszaad egy MatchData objektumot. Ez az objektum tartalmaz minden információt, ami az adott mintaillesztésről elérhető. Minden olyan információ, amit a $-változókon keresztül el lehet érni, megtalálható egy ilyen objektumban.

re = /(\d+):(\d+)/ # egy hh:mm formátumú időre illeszkedő kifejezés 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 illeszkedés adatai egy saját objektumban vannak eltárolva, ezért több különböző illeszkedés adatai is eltárolhatóak, és egyidőben hozzáférhetőek. Erre a korábban alkalmazott $-változók esetében nem volt lehetőség. A következő példában ugyanazt a Regexp objektumot két különböző string-re illesztjük. Minden egyes illeszkedés egy egyedi MatchData objektumot ad vissza, amit könnyen leellenőrizhetünk.

re = /(\d+):(\d+)/ # egy hh:mm formátumú időre illeszkedő kifejezés md1 = re.match("Time: 12:34am") md2 = re.match("Time: 10:30pm") md1[1, 2] # => ["12","34"] md2[1, 2] # => ["10","30"]

Felmerülhet ezután a kérdés, hogy hogyan jönnek a képbe a $-változók. Minden egyes mintaillesztés során, a Ruby az eredmény referenciáját (nil, vagy egy MatchData objektum) eltárolja egy változóba ($~-val elérhető). Ezután minden további $-változó ebből az egyből származik. Arról, hogy ez tényleg így van, a következő példa bizonyosodik meg. Megint két illeszkedést hajtunk végre, majd lekérjük a $-változók értékét. Ezután beállítjuk manuálisan a $~ változót, majd újra lekérjük a $-változók értékét.

re = /(\d+):(\d+)/ # egy hh:mm formátumú időre illeszkedő kifejezés md1 = re.match("Time: 12:34am") md2 = re.match("Time: 10:30pm") [ $1, $2 ] # az utolsó sikeres illeszkedés => ["10","30"] $~ = md1 [ $1, $2 ] # korábbi sikeres illeszkedés => ["12","34"]