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:

3 4
5 6
7 8

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:

7 11 15

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:

Concat
   enate

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.

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

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&lt;2 &amp;&amp; 4&gt;3")
puts unescapeHTML("&quot;A&quot; = &#65; = &#x41;")

Ennek eredménye:

1<2 && 4>3
"A" = A = A

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.

< Előző oldalKövetkező oldal >