Szép angol kifejezés a ,collection", amely gyûjteményt, kollekciót jelenthetne, de nekem igazából egyik sem tetszik. Ha valaki mégis magyarul akarja hallani, nevezzük ôket kupacoknak. Tehát a kupacok adatokat tartalmaznak valamiféle szervezett formában. Ezek az osztályok az OREXX adatkezelésének, illetve általában az OO nyelvek adatstruktúráinak alapjai, és az egyik vonzó dolog - szerintem - az objektum-orientált nyelvekben. Ezek az adatstruktúrák lehetôvé teszik, hogy adatkupacokat olyan formában tároljunk, hogy a lehetô legtakarékosabb legyen a tárolás, de fôleg a legegyszerûbb az adatok megtalálása és feldolgozása. Az egész játék két dolgon alapul:
- megjegyezni adatokat bizonyos szempontok szerint
- megtalálni a keresett adatokat (vagy azokra jellemzô információkat) bizonyos szempontok szerint.
Ezen ,bizonyos szempontok" alapján különböztethetjük meg a kupacfajtákat. De lássuk ôket valahogy úgy, hogy próbáljuk a ,rokonokat" egymáshoz közel tárgyalni!
Array
Vagyis tömb. A tömbök régi, jól ismert elemei a programoknak, nagyon kevés olyan programnyelv van, ahol nem találhatjuk meg ôket. A tömbökben rendezetten tároljuk az elemeket (ezt inkább úgy kell érteni, hogy az elemeknek van egy adott sorrendjük, eldönthetô hogy melyik elem után jön melyik másik elem), megtalálásukhoz számozott indexelést vehetünk igénybe, és - magától értetôdô, de ennek késôbb még lesz jelentôsége - minden index-elem (vagyis szám) egyszer szerepelhet. Az elemeket elérhetjük sorban (noha ennek itt nem sok értelme van) illetve közvetlen eléréssel az indexeiken keresztül. A tömbök lehetnek egy- vagy többdimenziósak is.
A tömböket létrehozhatjuk a szokásos módon, NEW üzenettel: tomb = .array~new. Ha elôre sejtjük, hogy mekkora lesz a tömb, itt megadhatjuk ezt, így gyorsítva az új elemek elhelyezését: tomb = .array~new(50,50). Ez a példa például egy kétdimenziós, ötvenszer ötvenes tömböt hoz létre, amely lehet ennél nagyobb is a késôbbiekben, de amíg az 50x50 alatt maradunk, az új elemek felvétele nem vesz el észrevehetô idôt.
Az egydimenziós tömböket létrehozhatjuk úgy is, hogy konstansként megadjuk az elemeket, sorban: tomb = .array~of('elso',2,'harmadik'), ami egy háromelemû tömböt hoz létre a megadott elemekkel. Az elemeket elhelyezhetjük a tomb~put(3,'harmadik elem') vagy a rövidebb tomb[3]='harmadik' formában, illetve törölhetjük azokat a remove üzenettel (vagy .NIL-t rakhatunk az elembe).
Az elemeket az indexükkel érhetjük el, ezek egytôl kezdôdô számok. Az elsô elem például tomb~at(1), illetve ha a kényelmes, más nyelvekbôl is megszokott jelölést akarjuk használni, akkor tomb[1]. Ha egy olyan elemet akarunk elérni, amelynek még nincs értéke, akkor az eredmény a .NIL objektum lesz. A tömbben levô elemek számát az items üzenettel tudhatjuk meg. Ha a tömböt nem folytonosan töltjük ki, tehát például csak a harmadik és a hetedik elembe teszünk valamit, akkor a first a legelsô, a last a legutolsó és a next, previous üzenet az adott index utáni illetve elôtti következô nem üres elemet adja vissza. Ilyenkor az items a valóban tömbben levô elemek számát adja vissza, míg a size a tömb által lefoglalt elemek számát, vagyis egydimenziós tömbnél a legnagyobb indexet (darabszám egytôl a maximális indexig), többdimenziósnál pedig a dimenziók nagyságának szorzatát, vagyis mindig azt a méretet, ahány elem befér a tömbbe anélkül, hogy bôvíteni kellene.
Ha valakiben felmerülne a kérdés, hogy ,milyen típusú elemeket lehet egy tömbben tárolni?", akkor emlékeztetem, hogy itt minden objektum, vagyis a tömb elemei akármilyenek lehetnek. Látható, hogy az objektum-orientáltság itt is milyen egyszerûvé teszi munkánkat.
Az array-nek (és a többi kupackezelônek) számos érdekes metódusa van még, amelyeket nem akarom most mind felsorolni, hiszen ezt megteszik a nyelvet teljesen leíró referenciák, de futólag megemlítenék párat. A makearray tömböt készít a kupacból, ami egy tömb esetében feleslegesnek hangzik. Azonban tudni kell, hogy ez az új tömb csak a létezô elemeket fogja tartalmazni! Igy tehát ,összetömöríthetünk" egy tömböt, a .NIL elemeket elhagyva. A section üzenettel egy darabot lehet kihasítani belôle, és a copy-val (amelyet már az Object class-tól örökölt) teljes másolatot készíteni róla.
Directory
Vagyis szótár. A szótárakban rendezetten tároljuk az elemeket, megtalálásukhoz szöveges (betûrendes) indexelést vehetünk igénybe, és minden index-elem (szó) egyszer szerepelhet. Az elemeket elérhetjük sorban illetve közvetlen eléréssel az indexeiken keresztül. A szótár - nevéhez hûen - a szótárakhoz hasonlít, ahol a szócikkek a címszavak alapján találhatóak meg. Az index tehát a tömbbel ellentétben itt nem szám, hanem szöveg, valamint logikusan nincs többdimenziós szótár. Nincs lehetôségünk konstansokkal létrehozni. Egy új elemet a tömbhöz hasonlóan a put, illetve a dir['elem']='ertek' formában adhatunk meg.
A tömbhöz képest itt rendelkezünk néhány érdekes metódussal: az ujdic=dir1~union(dir2) eredménye egy olyan szótár, amely tartalmazza mind a dir1 mind a dir2 szótár elemeit. (Pontosabban a dir2 nem csak szótár, hanem bármilyen kupac lehet; az egyetlen feltétel hogy ismerje a ,szokásos" kupac metódusokat.) A difference ehhez hasonló módon eredményezi azokat az elemeket, amik az elsôben megvannak de a másodikban nem. Az intersection azokat adja vissza, amelyek mindkettôben szerepelnek, a xor pedig azokat, amik csak az egyikben találhatóak meg. A subset metódus .TRUE értéket ad vissza, ha a dir1 minden eleme megtalálható dir2-ben, egyébként hamisat. Ezekkel a metódusokkal már igen sok érdekes dolgot lehet adatainkon elkövetni, minimális ,gépeléssel"!
A Directory Class felhasználására szép példákat találhatunk magában az OREXX-ben, hiszen például a már említett environment (környezet) objektumok is szótár struktúrában vannak tárolva.
Table
Azaz táblázat. A táblázatokban rendezetten tároljuk az elemeket, megtalálásukhoz az elemeket tetszôleges típusú objektumokkal indexeljük, és minden index-objektum egyszer szerepelhet. Az elemeket elérhetjük sorban illetve közvetlen eléréssel az indexeiken keresztül. Ami változott a szótárhoz képest az az, hogy itt az indexek szöveg helyet objektumok lettek, így megoldhatjuk például hogy egy objektumhoz ,hozzárendelünk" egy másikat. Mivel minden index csak egyszer szerepelhet, ha hozzárendelünk egy objektumhoz egy újat, akkor az törli a régi, ehhez az objektumhoz tartozó hozzárendelést. A rendelkezésünkre álló metódusok nem igazán térnek el a szótárétól, és az értékadás is ugyanúgy történik.
Relation
Ez itt nagyjából összefüggést, kapcsolatot jelent. A relationben rendezetlenül tároljuk az elemeket, megtalálásukhoz az elemeket tetszôleges típusú objektumokkal indexeljük, és minden index-objektumhoz hozzárendelhetünk tetszôleges számú elemet. Az elemeket közvetlenül érhetjük el az indexeiken keresztül. Ha egy indexhez több elem tartozik, ezeket meghatározatlan sorrendben kapjuk vissza egy tömbben. Látható, hogy a relation a táblához hasonlóan objektumokkal indexelt elemeket tartalmaz, azonban egy indexhez több elem is tartozhat, vagy más nézôpontból több egyforma indexe is lehet különbözô elemeknek. Az értékadás, a halmazmûveletek (mint a difference vagy az union) ugyanúgy mûködnek, kivéve a két kapcsolatokat feltáró metódust: az allat (nem, ez nem az ideges fejlesztôk ,állat" káromkodása, hanem az all+at, azaz a minden szó az at (-nál, -nél) szóhoz ragasztva), amely az indexhez tartozó összes elemet adja vissza egy tömbben, ahol az elemek sorrendje nem meghatározott; valamint az allindex, amely ennek a fordítottját végzi el, vagyis egy elemhez megadja, hogy mely indexek tartoznak hozzá. Az at() illetve a relation[index] módszer is használható, azonban kizárólag olyan indexekre, amelyekhez biztos, hogy kettônél kevesebb elem tartozik, ellenkezô esetben a visszaadott eredmény az indexhez tartozó valamelyik elem lesz. Azt, hogy egy indexhez hány elem tartozik, a relation~items(index) formában kaphatjuk meg a már ismert items metódus segítségével. A relation jól használható olyan adatbázisoknál ahol az elemek között többszörös, esetleg kétirányú összefüggések vannak.
List
Magyarul lista. A listában rendezetten tároljuk az elemeket, megtalálásukhoz azokat számokkal indexeljük (ezek az indexek azonban a lista minden változásánál változhatnak), és minden index egyszer szerepel (végül is sorszámok). Az elemeket sorban vagy közvetlen eléréssel érhetjük el. A lista tulajdonképpen egy olyan tömb (Array), amely nem csak a végén bôvíthetô, hanem tetszôleges helyen. Ha a listát csak a végén bôvítjük akkor tömbként mûködik, ha azonban a közepén helyezünk el elemeket, akkor attól a ponttól kezdve az elemek indexei megváltoznak. Az elemek törlése nem ,húzza össze" a listát - bár ezt egyszerûen megtehetjük a makearray metódussal, ha szükséges -, hanem csak .NIL objektumokkal jelzi az üres helyeket. Pont úgy lehet tehát a listát kezelni, mint a tömböket, kivétel az insert metódus, amellyel egy adott indexû, illetve paraméterként .NIL-t megadva az elso elem után szúrhatunk be új elemet.
Queue
Magyarul sor, de valójában a ,veremként" ismert adatstruktúrát (stack) is megvalósítja. A sorokban rendezetten tároljuk az elemeket. Az elemeket sorosan érhetjük el, és nem indexeiken keresztül.
A sort kétféleképp használhatjuk: sorként (Queue, FIFO [First In, First Out, vagyis ,elsô be, elsô ki"] veremként), amikor úgy mûködik, mint egy csô: az egyik végén bedobált elemeket a másik végén ugyanolyan sorrendben tudjuk kiszedni; és veremként, (Stack, LIFO [Last In, First Out] vagyis ,utolsó be, elsô ki" veremként), amikor úgy mûködik mint egy luk, amelybe berakhatunk elemeket, és mindig a legfelsôt (a legutoljára berakottat) tudjuk kivenni. A két módszer közötti különbség az, hogy az elemeket melyik metódussal helyezzük el a sorban. A queue metódus az elemet a sor végére helyezi el, és így a pull utasítás, amely mindig a sor elejérôl olvas, ugyanolyan sorrendben kapja meg az elemeket, mint ahogy betettük ôket. Ha pedig az elemek elhelyezésére a push metódust használjuk, amely a sor elejére helyezi az elemeket, akkor a pull fordított sorrendben fog olvasni, egy csinos kis vermet eredményezve. Valójában persze semmi akadálya annak, hogy mindkettôt egyszerre használjuk, és hogy hol ide - hol oda pakolgassunk. Az elemek nem csak a pull metódussal ismerhetôek meg - amely el is távolítja az adott elemet a sor elejérôl - hanem a peek-kel is, amely megmondja, hogy milyen elem áll a sor elején anélkül, hogy azt eltávolítaná. Sôt, füllentettem az elején, hiszen a sor elemei is elérhetôek ,indexeiken" keresztül, amely az elemek sorszáma a sor elejétôl számítva, és így módosíthatóak is, ugyanolyan formában mint ahogy a többi kupacfajta esetén. Ez a módszer azonban nem ,szép" módja a sor kezelésének-módosításának. Lássunk egy rövid példát:
/* queue.cmd: egy sor es egy verem */ sor = .queue~new verem = .queue~new do i=1 to 3 /* elemek be */ sor~queue(i'. elem') verem~push(i'. elem') end do 3 /* elemek ki */ say 'sor:'sor~pull'; verem:'verem~pull end
Set
Vagyis halmaz. A halmazokban rendezetlenül tároljuk az elemeket. Az elemeket nem indexeiken keresztül találhatjuk meg, hanem csak azt tudhatjuk meg, hogy egy adott elem szerepel-e a halmazban, vagy nem. Minden elem csak egyszer szerepelhet (vagyis ,vagy benne van a halmazban, vagy nem"). A halmaz tehát arra jó, hogy egy elemrôl eldöntsük, hogy része-e egy halmaznak vagy nem. Ez általában olyankor használható, amikor meg kell jegyezni hogy egy objektumról ,volt-e már szó vagy nem".
Technikailag a halmaz egy speciális tábla (a Table Class-ból örökölte a metódusait), ahol az elemek indexei maguk az elemek, így annak eldöntése, hogy szerepel-e egy elem annyit jelent, hogy megvizsgáljuk, hogy az elemhez tartozó index .NIL-t tartalmaz-e, vagy magát az elemet. Persze lehetôség van a ,sokkal szebb" hasindex metódus használatára, amely igaz értéket ad vissza akkor, ha van ilyen indexû elem a halmazban, és hamisat egyéb esetben. Emellett persze minden halmaz rendelkezik a tábla osztály metódusaival is, amelybôl nyilván a halmazmûveletek (difference, union, intersection, xor és subset) számunkra a legfontosabbak. Az elemek halmazban való elhelyezésére itt a put metódus praktikusabb, mint a halmaz[elem]=elem forma, ugyanis ez utóbbinál kötelezô az, hogy az index és a hozzárendelt érték megegyezzen, egyébként hibajelzés a jutalmunk.
Bag
Magyarul zsák. A zsákban rendezetlenül tároljuk az elemeket. Az elemeket nem indexeiken keresztül találhatjuk meg, hanem csak azt tudhatjuk meg, hogy egy adott elem szerepel-e a zsákban, és ha igen, hányszor. Ebbôl következik hogy a zsákban szerepelhet több egyforma elem is. A zsák technikailag a Relation Class édesgyermeke, így annak metódusait kapta örökségül. A halmazhoz hasonlóan itt is arról van szó, hogy az elemek saját maguk indexei, vagyis itt is érvényes az, hogy például közvetlen értékadásnál az indexnek és a hozzá rendelt tartalomnak egyeznie kell. Az elemeket itt is a put metódussal a legegyszerûbb bedobálni a zsákba. Azt, hogy hány darab objektum van a zsákban az items metódussal tudhatjuk meg, és ugyanezt használhatjuk annak eldöntésére hogy egy adott fajta objektumból hány darab található a zsákban. Hogy egy objektum egyáltalán a zsákban van, azt - azon kívül, hogy az at illetve a [] paranccsal megvizsgáljuk, vagy az items-nél megnézzük hogy nulla darab van-e belôle - megtudhatjuk a hasindex nevû, a halmaznál már megismert metódussal, amely a legkényelmesebb módja ennek. A zsákoknál használhatjuk a relation metódusait is mindenféle vizsgálódásra - több-kevesebb haszonnal. Felhasználási területe a halmazokéhoz hasonló, olyan esetekben, amikor azzal ellentétben azt is nyilván kell tartani hogy az adott objektum hányszor került a figyelem középpontjába, sorrendre való tekintet nélkül.
Végére értünk hát a kupacoknak. Látható, hogy igen gazdag, sokrétû eszköztárral állunk szemben. Ezek használatával sok bonyolult feladatot oldhatunk meg egyszerûen és látványosan (és az sem utolsó szempont, hogy sokkal gyorsabban, mintha azt egy REXX programrészlettel végeztetnénk el).
Így, hogy már ismerjük a különbözô fajtákat, tegyünk említést azokról a lehetôségekrôl, amelyek minden kupacra alkalmazhatóak! Elôször is megemlítem az egyenlôségvizsgálatokat (=, ==, \= és társai), amelyek nem igazán használhatóak arra, amire szeretnénk, ugyanis azonosan egyezôséget vizsgál, vagyis csak akkor egyenlô az egyik objektum a másikkal, ha annyira ugyanazok, hogy amikor az egyiket változtatjuk akkor változik a másik is. (Ezt az objektum2=objektum1 módszerrel érhetjük el. Ha nem ez a célunk - és többnyire nem -, akkor az objektum2=objektum1~copy formában tudunk önálló életre is képes másolatot csinálni, amely azonban már nem lesz egyenlô az eredetijével...) Az azonossági vizsgálatokra sokkalta alkalmasabbak a halmazmûveletek, név szerint a xor vagy a difference, amelyeknél .NIL eredmény jelenti az egyezôséget. Természetesen mindenütt mûködik a put és az értékadás egyenlôségjellel, valamint az at és az érték keresése szögletes zárójelekkel.
Ezen metódusokon kívül van azonban még egy lehetôség, amelyet a kupacokkal kapcsolatban nagyon hasznos ismerni, és ez a DO utasítás DO OVER formája. Nézzük az alábbi programrészletet:
/* doover.cmd: do over kupac */
kupac = .relation~new
kupac['os2']='multithread'
kupac['os2']='multitask'
kupac['linux']='multitask'
/* kiirunk minden os2-hoz tartozo tulajdonsagot */
do kepesseg over kupac~allat('os2')
say 'OS/2='kepesseg
end
/* kiirunk minden ismert indexet (oprendszert) */
do darab over kupac
say darab
end
/* ez ugyanaz, csak mutatom hogy hogyan mukodik az over */
atmenet1 = kupac~makearray
do index=atmenet1~first to atmenet1~last
darab=atmenet1~at(index)
say darab
end
Látható futás után, hogy az over sorban végigmegy a kupac elemein, és azokat beteszi a megadott változóba. Ezzel a módszerrel végig tudjuk járni egy nem egyértelmûen rendezett (pl. zsák vagy tábla) elemeit is. Az, hogy az over eredménye a kupacban található adat, vagy csak azok indexe, attól függ, hogy a kupacra hogyan értelmezett a makearray metódus: a tömböknél ez az elemeket eredményezi, míg a példánkban a relationnél az indexeket adta vissza.
További hasznos osztályok
A kupacokon kívül sok olyan osztállyal is rendelkezünk indulásnál, amelyek szorosan véve nem adatokat tárolnak, hanem vagy objektumokon végeznek mindenféle varázslatot, vagy a rendszer lehetôségeit veszik igénybe, illetve segítenek ezeket egyszerûen felhasználni.
Tekintsük elôször azokat, amelyek a kupacokhoz hasonlóan ,adatstruktúrák", adatokat tárolnak!
String
Akit néha magyar barátai karakterláncnak szoktak csúfolni. Mint ismert, az eredeti REXX nyelv alapja a String adatstruktúra volt, és az OREXX esetén sem hanyagoljuk el a használatát. A stringek tetszôleges hosszúságúak lehetnek és emellett tetszôleges értékû byte-okat tartalmazhatnak. (Számunkra inkább érdekesség csupán az, hogy a REXX stringek a megfelelô fajtájú OS/2 alatt tartalmazhatnak DBCS [Double Byte Character Set] jeleket is, amelyek két byte-on tárolt, többnyire kínai, japán és hasonló nyelvû karakterek. Ezek kezelése többnyire nem is igényel külön elbánást, ha pedig mégis, arról a megfelelô leírásokból érdemes tájékozódni, amennyiben programunkat ilyen környezetben óhajtanánk felhasználni.)
A String osztály gazdagon el van látva metódusokkal; megtalálhatunk itt mindent ami egyrészt a karakterláncok kezeléséhez szükséges (ezek a metódusok a ,régi" REXX guruk számára ismerôsek lesznek, hiszen nagy részüket mint karakterláncokkal foglalkozó függvényt már ismerhetik, és ezen metódusok nagy része az OREXX-ben is megtalálható függvényként), másrészt azon alapvetô numerikus mûveleteket, amelyekkel a ,számokként értelmezhetô karakterláncokon" végezhetünk mûveleteket. Metódusait itt nem sorolnám fel, mert egyrészt azok megtalálhatóak a megfelelô referencia-file-okban, másrészt igen sok van belôlük.
Érdemes azt is felidézni, hogy a String class az egyik népszerû ,közös nevezô" az osztályok között; minden osztály rendelkezik a string metódussal, amellyel többé-kevésbé sikeresen próbálja elôállítani az objektum szöveges formáját, és - ha még mindenki emlékszik rá - sok objektumon végzett mûvelet - mint legtipikusabb példaként a SAY utasítás - automatikusan ,odagondolja" a string metódust, hogy az objektum szöveges (vagy numerikus, hiszen ez ugyanúgy string) formájával dolgozhasson tovább.
Stem
Na igen, meg sem próbálnám lefordítani (remélem mások sem) ennek az osztálynak a nevét, amely már REXX idôkben is hallatlanul érdekes programozási megoldásokat provokált ki. A Stemek furcsa jószágok, kicsit olyanok, mintha tömbök lennének, kicsit mint a Directory class hagyományos REXX-be öntve, és kicsit valami más. A Stemekrôl tudnék sok mágikus dolgot mesélni, de egyrészt ezek tényleg csak a REXX-ôrülteknek lennének érdekesek, másrészt a stem-bûvészkedést kicsit háttérbe szorították az újonnan megjelent kupac adatstruktúrák. (Ennek ellenére a BBS-en és Interneten elérhetô példaprogramok közé betettem egy stems.cmd-t amelyben ilyesmik történnek.)
A stem tulajdonképpen tetszôleges stringgel indexelt tömb, stemnév.index1.index2.... formában. Maga a Stem a stemnév. (a pötty is!), ha ennek adunk értéket, akkor a stem minden eleme ezt az értéket veszi fel. Az indexek (ha szerepelnek) változónevek. Mivel a változók alapértelmezésben saját nevüket tartalmazzák nagybetûvel, így a nem létezô változók indexként megadva úgy mûködnek, mintha nem egy változót, hanem az index nevét írtuk volna oda. Példa: a STEM.I az ,I" indexû elemet jelenti, ha az I változónak nincs értéke, és például a hatos indexût, ha az I értéke 6.
Mint objektumosztály a Stem nem igazán hasznos dolog, néhány egyszerû metóduson kívül nem sok mindent tudunk vele csinálni, az igazi ,erejét" a struktúrált programozásnál (,hagyományos programok", amelyek hatvan fokon még nem mosták ki a GOTO foltokat, és a gépünk is kifakult tôlük) érezhettük.
| Gervai Péter 1997. 02. 08. | [ Elôzô lecke | Következô lecke | Tartalom ] |