8. Programozás tételek felsoroló típusokra Ha egy adatot elem értékek csoportja reprezentál, akkor az adat feldolgozása ezen értékek feldolgozásából áll. Az lyen adat típusának lényeges jellemzője, hogy az őt reprezentáló elem értékeknek m az egymáshoz való vszonya, a reprezentácó mlyen jellegzetes belső szerkezettel rendelkezk. Számunkra különösen azok a típusok érdekesek, amelyek típusértéket azonos típusú elem értékek sokasága reprezentál. Ilyen például egy tömb, egy halmaz vagy egy sorozat. Ezeknek az úgynevezett gyűjteményeknek közös tulajdonsága, hogy a bennük tárolt elem értékek egymás után felsorolhatók. Egy halmazból egymás után kvehetjük, egy sorozatnak vagy egy tömbnek véggnézhetjük az elemet. Éppen ezért az lyen típusokat szokták felsorolhatónak (enumerable) vagy teráltnak (terált szerkezetűnek) nevezn. Felsoroln nemcsak terált szerkezetű adatok elemet lehet, hanem egy egész szám valód osztót, vagy két egész szám által meghatározott zárt ntervallum egész számat. Ebből s látszk, hogy nagyon sokféle felsorolható adat van. A felsorolás tehát nem csak a felsorolhatósághoz kötődk. Általánosságban azt jelent, hogy képesek vagyunk egy adatnak valamlyen értelemben vett első elemére ráálln, majd a soron következőre, meg tudjuk kérdezn, van-e egyáltalán első vagy soron következő elem, és lekérdezhetjük a felsorolás során éppen aktuáls elemnek az értékét. A felsorolást végző műveletek nem ahhoz az adathoz tartoznak, amelynek elemet felsoroljuk, legfeljebb csak támaszkodnak az adat műveletere. Furcsa s lenne, ha egy egész szám (amelyknek valód osztóra vagyunk kíváncsak) alapból rendelkezne lyen ( vedd az első valód osztót, vedd a következő valód osztót, van-e még valód osztó, m az éppen vzsgált valód osztó ) műveletekkel. De egy egész ntervallumnak s csak olyan művelete vannak, amvel az ntervallum határat tudjuk lekérdezn, az ntervallum egész számanak felsorolásához már egy specáls objektumra, egy ndexre van szükség. Ráadásul egy ntervallum felsorolása többféle lehet (egyesével vagy kettesével; növekvő, esetleg csökkenő sorrendben), ezeket mnd nem lehetne az ntervallum típusműveletevel leírn. A felsorolást végző műveleteket ezért mndg egy külön objektumhoz kötjük. Ha szükség van egy adat elem érékenek felsorolásra, akkor az adathoz hozzárendelünk egy lyen felsoroló objektumot. Egy felsoroló objektum feldolgozása azt jelent, hogy az általa felsorolt elem értékeket valamlyen tevékenységnek vetjük alá. Ilyen tevékenység lehet ezen értékek összegzése, adott tulajdonságú értékek megszámolása vagy a legnagyobb elem érték megkeresése, stb. Ezek ugyanolyan feladatok, amelyek megoldására korábban programozás tételeket vezettünk be, csakhogy azokat a tételeket ntervallumon értelmezett függvényekre fogalmaztuk meg, most vszont ennél általánosabb, felsorolóra kmondott változatukra lenne szükség. Ezt az általánosítást azonban könnyű megtenn, hszen egy ntervallumhoz nem nehéz felsorolót rendeln, így a korábban bevezetett ntervallumos tételeket egyszerűen átfogalmazhatjuk felsoroló objektumra. Először (8.1. alfejezet) a nevezetes típusszerkezeteket fogjuk megvzsgáln, de ezek között s a legnagyobb hangsúlyt az terált szerkezetű, tehát felsorolható típusok bemutatására helyezzük. A 8.2. alfejezetben a felsoroló típus műveletet fogjuk jellemezn, majd megadjuk egy felsoroló objektum általános feldolgozását végző algortmus-sémát. A 8.3. alfejezetben 107
8. Programozás tételek felsoroló típusokra nevezetes felsoroló típusokat mutatunk. A 8.4. alfejezetben a programozás tételenket mondjuk k felsoroló típusokra. 8.1. Gyűjtemények A programozás feladatok megoldásában leggyakrabban gyűjtemények elem értéket kell felsoroln és feldolgozn. Gyűjteményeknek azokat az terált szerkezetű adatokat tekntjük, amelyek típusértéket azonos típusú elem értékek sokasága reprezentál. Vzsgáljunk meg most néhány nevezetes gyűjteményt. A halmaz (szerkezetű) típus típusértéket egy-egy véges elemszámú 2 E -bel elem (E-bel elemekből képzett halmaz) reprezentálja. Egy halmaz típusú objektumnak a típusművelete a halmazok szokásos művelete lesznek. Értelmezzük: halmaz ürességének vzsgálatát (h=, ahol h egy halmaz típusú értéket jelöl), halmaz egy elemének kválasztását (e: h, ahol e egy elem értéket hordozó segédváltozó), halmazból egy elem kvonását (h:=h {e}), új elemnek a hozzáadását a halmazhoz (h:=h {e}). A típus-megvalósítás szempontjából egyáltalán nem közömbös, hogy tt a szokásos unó lletve kvonás műveletével van-e dolgunk, vagy a megkülönböztetett (dszjunkt) unó lletve megkülönböztetett kvonással. Ez utóbbak esetén feltételezzük, hogy a művelet megváltoztatja a halmazt, azaz unó esetén bővül (mert a hozzáadandó elem új, még nncs benne a halmazban), kvonás esetén fogy (mert a kvonandó elem benne van a halmazban). Ezeknek a műveleteknek az mplementálása egyszerűbb, mert nem kell ezen feltételeket külön ellenőrznük, gaz, a feltétel nem teljesülése estén abortálnak. A halmaz típust a set(e) jelöl. Látjuk, hogy egy halmaz egy elemének kválasztása (e: h) a nem-determnsztkus értékkválasztással történk, amt ugyanazon halmazra egymás után alkalmazva nem fogjuk ugyanazt az elemet megkapn. Be lehet vezetn azonban a determnsztkus elemkválasztás műveletét (e:=mem(h)), amelyet ha egy halmazra többször egymás után hajtjuk végre úgy, hogy közben a halmazt nem változtatjuk meg, akkor mndg ugyanazon elemét adja vssza a halmaznak. Az gazat megvallva, a halmaz szóba jöhető reprezentácó sokkal nkább támogatják ennek az elemkválasztásnak a megvalósítását, mnt a valóban véletlenszerű, nemdetermnsztkus értékkválasztást. A sorozat (szerkezetű) típus típusértéket egy-egy véges hosszú E * -bel elem (E-bel elemekből képzett sorozat) reprezentálja, típusművelete pedg a sorozatokon értelmezhető műveletek. Jelöljön a t egy sorozat típusú objektumot, amelyet egy sorozat reprezentál. Most a teljesség génye nélkül felsorolunk néhány típusműveletet, amelyeket a t-re szoktak bevezetn, és a t-t reprezentáló sorozaton értelmezhetőek. Ilyen egy sorozat hosszának lekérdezése ( t ), a sorozat valahányadk elemére ndexeléssel történő hvatkozás (t ahol az ndexnek 1 és a sorozat hossza közé kell esn), egy elem törlése egy sorozatból vagy egy új elem beszúrása. Magát a sorozat típust a seq(e) jelöl. Specáls sorozattípusokhoz jutunk, ha csak bzonyos típusműveleteket engedünk meg. Ilyen például a verem és a sor. A verem esetén csak a sorozat elejére szabad új elemet befűzn, a sornál pedg csak a sorozat végére. Mndkettő esetén megengedett művelet annak eldöntése, hogy a reprezentáló sorozat üres-e. Mndkettőnél k lehet olvasn a sorozat első elemét, és azt el s lehet hagyn a sorozatból. A szekvencáls outputfájl (jelölése: outfle(e)) egyetlen műveletet enged meg: a sorozat végéhez új elem vagy elemekből képzett sorozat hozzállesztését (jelölése: wrte(e) vagy wrte(<e 1,,e n >)). A szekvencáls nputfájlnak (jelölése: nfle(e)) s egyetlen művelete van: a sorozat első elemének lefűzése, más szóval az 108
8.1. Gyűjtemények olvasás művelete. Matematka értelemben ezt egy olyan függvénnyel írhatjuk le, amely egy sorozat típusú objektumhoz (pontosabban az őt reprezentáló sorozathoz) három értéket rendel: az olvasás státuszát, a sorozat első elemét (ha van lyen), és az első elemétől megfosztott sorozatot. Az olvasást az st,e,t:=read(t) értékadással, vagy rövdítve az st,e,t:read szmbólummal jelöljük. Itt az st az olvasás státuszát kapja. Ez egy specáls kételemű halmaznak (Státusz ={abnorm, norm}) az egyk eleme. Ha a t eredet értéke egy üres sorozat volt, akkor az st változó az abnorm értéket vesz fel, különben a norm-ot. Ha a t-bel eredet sorozat nem üres, akkor az e az eredet sorozat első elemét, a t az eggyel rövdebb sorozatot vesz fel, egyébként a t-bel sorozat továbbra s üres marad, az e pedg defnálatlan. Sorozat szerkezetűnek teknthetjük a vektor típust, vagy más néven egydmenzós tömböt. Ha eltekntünk egy pllanatra attól, hogy a vektorok tetszőlegesen ndexelhetőek, akkor a vektort egy olyan sorozatnak teknthetjük, amelynek az elemere a sorszámuk alapján lehet közvetlenül hvatkozn, de a sorozat hossza (törléssel vagy beszúrással) nem változtatható meg. Egy vektor típusához azonban a fent vázolt sorozaton kívül azt az ndextartományt s meg kell adn, amely alapján a vektor elemet ndexelhetjük. A vektor típus tehát valójában egy rögzített hosszúságú sorozatból és egy egész számból álló rekord, amelyben a sorozat tartalmazza a vektor elemet, az egész szám pedg a sorozat első elemének ndexét adja meg. A vektor típust a vec(e) jelöl. A vektor alsó és felső ndexének lekérdezése s éppen úgy típusműveletnek teknthető, mnt a vektor adott ndexű elemére való hvatkozás. Ha v egy vektor, pedg egy ndexe, akkor v[] a vektor ndexű eleme, amt lekérdezhetünk vagy megváltoztathatunk, azaz állhat értékadás jobb vagy baloldalán. Általánosan a v vektor ndextartományának elejét a v.lob, végét a v.hb kfejezések adják meg. Ha azonban a vektor m..n típusra az általános vec(e) jelölés helyett továbbra s a korábban bevezetett E jelölést alkalmazzuk, akkor az ndextartományra egyszerűen az m és n segítségével hvatkozhatunk. Vlágosan kell azonban látn, hogy tt az m és az n a vektor egyed tulajdonsága, és nem attól független adatok. A kétdmenzós tömbtípus, azaz a mátrx típus modellünkben vektorok vektoraként fogalmazható meg könnyen. Ennek megfelelően a t mátrx -edk sorának j-edk elemére a t[][j] hvatkozk (ezt rövdebben t[,j]-vel s jelölhetjük), az -edk sorra a t[], az első sor ndexére a t.lob, az utolsóéra a t.hb, az -edk sor első elemének ndexére a t[].lob, az utolsó elem ndexére a t[].hb. A mátrx típust a matrx(e) jelöl. (Ezek a műveletek általánosítható a kettőnél több dmenzós tömbtípusokra s.) Specáls, de a leggyakrabban alkalmazott mátrx az, amelynek sora egyforma hosszúak és ugyanazzal az ndextartománnyal ndexeltek. Ezt l.. n k.. m jelöl az E, ahol a sorokat az [l..n] ntervallum, egy sor elemet pedg a [k..m] n m ntervallum ndexel. Ha k=1 és l=1, akkor az E jelölést s használjuk és lyenkor n*m-es mátrxokról (sorok száma n, oszlopok száma m) beszélünk. Könyvünkben megengedettnek tekntjük mndazokat a típusokat, amelyeket megengedett típusokból az tt bemutatott típusszerkezetek segítségével készíthetünk. 109
8. Programozás tételek felsoroló típusokra 8.2. Felsoroló típus specfkácója Most általánosan jellemezzük az olyan objektumokat (ezeket hívjuk majd rövden felsorolónak), amelyek segítségével egy felsorolható adatnak (vektornak, halmaznak, szekvencáls nputfájlnak, valód osztót felkínáló természetes számnak) az elemet egymás után elő lehet állítan. Egy felsoroló objektum 1 (enumerator) véges sok elem érték felsorolását tesz lehetővé azáltal, hogy rendelkezk a felsorolást végző műveletekkel: rá tud álln a felsorolandó értékek közül az elsőre vagy a soron következőre, meg tudja mutatn, hogy tart-e még a felsorolás és vssza tudja adn a felsorolás során érntett aktuáls értéket. Ha egy típus ezeket a műveleteket bztosítja, azaz felsoroló defnálására képes, akkor azt felsoroló (enumerator) típusnak nevezzük. Egy t felsoroló típusú objektumra defnícó szernt négy műveletet vezetünk be. A felsorolást mndg azzal kezdjük, hogy a felsorolót a felsorolás során először érntett elem értékre feltéve, hogy van lyen állítjuk. Ezt általánosan a t:=frst(t) értékadás valósítja meg, amt a továbbakban t.frst() 2 -tel jelölünk. Mnden tovább, tehát soron következő elemre a t.next() művelet (amely a t:=next(t) rövdített jelölése) segítségével tudunk ráálln. Vegyük észre, hogy mndkettő művelet megváltoztatja a t felsoroló állapotát. A t.current() művelet a felsorolás alatt kjelölt aktuáls elem értéket adja meg. A t.end() a felsorolás során mndaddg hams értéket ad vssza, amíg van kjelölt aktuáls elem, a felsorolás végét vszont gaz vsszaadott értékkel jelz. Ez a két utóbb művelet nem változtathatja meg a felsoroló állapotát. Fontos krtérum, hogy a felsorolás vége véges lépésben (a t.next() véges sok végrehajtása után) bekövetkezzék. A felsoroló műveletek hatását általában nem defnáljuk mnden esetre. Például nem-defnált az, hogy a t.frst() végrehajtása előtt (tehát a felsorolás kezdete előtt) lletve a t.end() gazra váltása után (azaz a felsorolás befejezése után) m a hatása a t.next(), a t.current() és a t.end() műveleteknek. Általában nem defnált az sem, hogy m történjen akkor, ha a t.frst() műveletet a felsorolás közben smételten végrehajtjuk. Mnden olyan típust felsorolónak nevezünk, amely megfelel a felsoroló típusspecfkácónak, azaz mplementálja a Frst(), Next(), End() és Current() műveleteket. A felsoroló (enumerator) típust enor(e)-vel jelöljük, ahol az E a felsorolt elem értékek típusa. Ezt a jelölés alkalmazhatjuk a típus értékhalmazára s. Egy felsoroló objektum hátterében mndg elem értékeknek azon véges hosszú sorozatát látjuk, amelynek elemet sorban, egymás után be tudjuk járn, fel tudjuk soroln. Ezért specfkácós jelölésként megengedjük, hogy egy t felsoroló által felsorolható elem értékre úgy hvatkozzunk, mnt egy véges sorozat elemere: a t a felsorolás során -edkként felsorolt elem érték, ahol az 1 és a felsorolt elemek száma (jelöljük ezt t -vel) közé eső egész szám. Hangsúlyozzuk, hogy a felsorolható elemek száma defnícó szernt véges. Ezzel tulajdonképpen egy absztrakt 25 1 Amkor a felsorolható adat egy gyűjtemény (terált), akkor a felsoroló objektumot szokták bejárónak vagy terátornak s nevezn, míg maga a felsorolható gyűjtemény a bejárható, azaz terálható adat. 2 A műveletek jelölésére az objektum orentált stílust használjuk: t.frst() a t felsorolóra vonatkozó Frst() műveletet jelöl. 110
8.2. Felsoroló típus specfkácója megvalósítást s adtunk a felsoroló típusnak: a felsorolókat a felsorolandó elem értékek sorozata reprezentálja, a felsorolás műveletet ezen a sorozat bejárásával mplementáljuk. A felsoroló típus konkrét reprezentácójában mndg megjelenk valamlyen hvatkozás arra az adatra, amelyet felsoroln kívánunk. Ez lehet egyetlen természetes szám, ha annak az osztót kell előállítan, lehet egy vektor, szekvencáls nputfájl, halmaz, esetleg multplctásos halmaz (zsák), ha ezek elemenek felsorolása a cél, vagy akár egy gráf, amelynek a csúcsat valamlyen stratégával be kell járn, hogy az ott tárolt értékekhez hozzájussunk. A reprezentácó ezen az érték-szolgáltató adaton kívül még tartalmazhat egyéb, a felsorolást segítő komponenseket s. A felsorolás során mndg van egy aktuáls elem érték, amelyet az adott pllanatban lekérdezhetünk. Egy vektor elemenek felsorolásánál ehhez elég egy ndexváltozó, egy szekvencáls nputfájl esetében a legutoljára kolvasott elemet kell tároln lletve, azt, hogy skeres volt-e a legutolsó olvasás, az egész szám osztónak felsorolásakor például a legutoljára megadott osztót. Egy felsoroló által vsszaadott értékeket rendszernt valahogyan feldolgozzuk. Ez a feldolgozás gen változatos lehet; jelöljük ezt most általánosan az F(e)-vel, amely egy e elem értéken végzett tetszőleges tevékenységet takar. Nem szorul különösebb magyarázatra, hogy a felsorolásra épülő feldolgozást az alább algortmus-séma végz el. Megjegyezzük, hogy mvel a felsorolható elemek száma véges, ezért ez a feldolgozás véges lépésben garantáltan befejeződk. t.frst() t.end() F( t.current() ) t.next() 8.3. Nevezetes felsorolók Az alábbakban megvzsgálunk néhány fontos felsoroló típust, olyat, amelynek reprezentácója valamlyen nevezetes egy kvételével terált típusra épül. Vzsgálatanknak fontos része lesz, hogy megmutatjuk egy-egy konkrét felsoroló típusú objektum esetén azt az általános feldolgozást végző algortmus-sémát, amellyel be lehet járn a felsorolt értékeket. Természetesen mnden esetben k fogunk térn arra, hogy a vzsgált típus hogyan feleltethető meg a felsoroló típusspecfkácónak, azaz m a felsorolást bztosító Frst(), Next(), End() és Current() műveletek mplementácója. Tekntsük először az egész-ntervallumot felsoroló típust. Itt egy [m..n] ntervallum elemenek klasszkus, m-től n-g egyesével történő felsorolására gondolunk. Természetesen ennek mntájára lehet defnáln a fordított sorrendű vagy a kettesével növekedő felsorolót s. Az egész számok ntervallumát nem tekntjük terált szerkezetűnek, hszen a reprezentácójához elég az ntervallum két végpontját megadn, művelete pedg ezeket az ntervallumhatárokat kérdezk le. Ugyanakkor mndg fel lehet soroln az ntervallumba eső számokat. Az egész-ntervallumra épülő felsoroló típus attól különleges számunkra, hogy a 111
8. Programozás tételek felsoroló típusokra korábban bevezetett programozás tételenket s az egész számok egy ntervallumára fogalmaztuk meg, amelyeket ennek közvetítésével más felsoroló típusú objektumokra s k tudunk majd terjeszten. A felsoroló típus egy típusértékét egy [m..n] ntervallum két végpontja (m és n) és az ntervallum elemenek felsorolását segítő egész értékű ndexváltozó () reprezentálja. Az változó az [m..n] ntervallum aktuálsan kjelölt elemét tartalmazza, azaz mplementálja a Current() függvényt. A Frst() műveletet az :=m értékadás, a Next() műveletet az :=+1 értékadás váltja k. Az >n helyettesít az End() függvényt. (A Frst() művelet tt smételten s kadható, és mndg újrandítja a felsorolást, mnd a négy művelet bármkor, a felsoroláson kívül s termnál.) Könnyű végggondoln, hogy mként lehetne az ntervallumot fordított sorrendben ( :=n, :=-1, <m), vagy kettesével növekedően (:=m, :=+2, >n) felsoroln. Ezek alapján a felsoroló objektum feldolgozását végző általános algortmus-sémából előállítható az egész-ntervallum normál sorrendű feldolgozását végző algortmus, amelyet számlálós cklus formájában s megadhatunk. :=m n F() :=+1 := m.. n F() Magától értetődően lehet sorozatot felsoroló típust készíten. (Itt s a klasszkus, elejétől a végég tartó bejárásra gondolunk, megjegyezve, hogy más bejárások s vannak.) A reprezentácó lyenkor egy sorozat típusú adat (s) mellett még egy ndexváltozót () s tartalmaz. A sorozat normáls (elejétől végég történő) bejárása során az egész típusú ndexváltozót az 1.. s ntervallumon kell végg vezetn éppen úgy, ahogy ezt az előbb láttuk. Ekkor az s -t teknthetjük az Current() kfejezésnek, az :=1 a Frst() művelet lesz, az :=+1 a Next() művelet megfelelője, az > s pedg az End() kfejezéssel egyenértékű. (A Frst() művelet smételten s kadható, és mndg újrandítja a felsorolást, a Next() és End() bármkor befejeződk, de a Current()csak a felsoroláson alatt termnál.) Ezek alapján az s sorozat elemenek elejétől végég történő felsorolását végző programot az alább két alakban írhatjuk fel. :=1 s F(s ) :=+1 :=1.. s F(s ) A vektort (klasszkusan) felsoroló típus a sorozatokétól csak abban különbözk, hogy tt nem egy sorozat, hanem egy v vektor bejárása a cél. A bejáráshoz használt ndexváltozót 112
8.3. Nevezetes felsorolók (jelöljük ezt most s -vel) a bejárandó v vektor ndextartományán (jelöljük ezt [m..n]-nel) kell véggvezetünk, és az aktuáls értékre a v[] formában hvatkoznunk. Ennek értelmében az v[]- t teknthetjük a Current() műveletnek, az :=m tulajdonképpen a Frst() művelet lesz, az :=+1 a Next() művelet megfelelője, az >n pedg a End() kfejezéssel egyenértékű. (A Frst() művelet smételten s kadható, és mndg újrandítja a felsorolást, a Next() és End() bármkor befejeződk, de a Current()csak a felsoroláson alatt termnál.) Egy vektor elemenek feldolgozását az ntervallumhoz hasonlóan kétféleképpen írjuk fel: :=m n F(v[]) :=+1 :=m.. n F(v[]) Egy vektoroknak (akárcsak a korábban látott ntervallumnak és sorozatnak) nemcsak egyféle bejárása lehetséges. Megengedett rájuk például a vsszafelé haladó bejárás, vagy a kettesével történő bejárás s. Sőt valójában mndenféle bejárást értelmezhetünk rájuk. (Könyvünkben úgy tekntünk a vektor ndextartományára, mnt egy egész ntervallumra. A vektor típus fogalma azonban általánosítható úgy, hogy ndextartományát egy felsoroló objektum írja le. Ilyenkor a vektort felsoroló objektum művelete megegyeznek az ő ndextartományát felsoroló objektum műveletevel egyet kvéve: a Current() műveletet a v[.current()] mplementálja, ahol az ndextartomány elemet felsoroló objektumot jelöl.) Mvel a mátrx vektorok vektora, ezért nem meglepő, hogy mátrxot (sorfolytonosan) felsoroló típust s lehet készíten. Ez a mátrx esetén az egyk leggyakrabban alkalmazott felsorolás, amkor először az első sor elemet, azt követően a másodkat, és így tovább, végül az utolsó sort járjuk be. Egy a jelű n*m-es mátrx (azaz téglalap-mátrx, ahol mnden sor egyformán m hosszú) lyen sorfolytonos bejárásánál a mátrxot felfoghatjuk egy n*m elemű v vektornak, ahol mnden 1 és n*m közé eső k egész számra a v[k] = a[((k-1) dv m) +1, ((k-1) mod m) +1]. Ezek után a bejárást a vektoroknál bemutatott módon végezhetjük. Egyszerűsödk a fent képlet, ha a vektor és a mátrx ndextartományat egyaránt 0-tól kezdődően ndexeljük. Ilyenkor v[k] = a[k dv m, k mod m], ahol a k a 0..n*m-1 ntervallumot futja be. A mátrx elemenek sorfolytonos bejárása így gen egyszerű lesz, bár nem ez az általánosan smert módszer. k := 0.. n*m-1 F(a[k dv m, k mod m]) Mvel a mátrx egy kétdmenzós szerkezetű típus, ezért a bejárásához az előbb bemutatott módszerrel szemben két ndexváltozót szoktak használn. (Más szóval a mátrx 113
8. Programozás tételek felsoroló típusokra bejárót egy mátrx és két ndexváltozó reprezentálja.) Sorfolytonos bejárásnál az egyket a mátrx sora között bejárásra, a máskat az aktuáls sor elemenek a bejárására használjuk. A bejárás során a[,j] lesz a Current(). Először a a[1,1]-re kell lépn, így a Frst() műveletet az,j:=1,1 mplementálja. A soron következő mátrxelemre egy elágazással léphetünk rá. Ha a j bejáró még nem érte el az aktuáls sor végét, akkor azt kell eggyel megnöveln. Ellenkező esetben az bejárót növeljük meg eggyel, hogy a következő sorra lépjünk, a j bejárót pedg a sor elejére állítjuk. Összességében tehát az IF(j<m: j:=j+1; j=m:,j:=+1,1) elágazás mplementálja a Next() műveletet. Az End() kfejezést az >n helyettesít. (Ez a megoldás könnyen általánosítható nem téglalap-mátrxra s, ahol a sorok eltérő hosszúak és eltérő ndexelésűek.),j:=1,1 n F( a[,j] ) j<m j:=j+1,j:=+1,1 Ennek a megoldásnak a gyakorlatban jobban smert változata az alább kétcklusos algortmus. Itt az,j:=1,1 értékadást (Frst()) a két egymásba ágyazott cklus ncalzáló lépése végz el. A Next() műveletet megvalósító elágazás feltétele (j m) a belső cklus cklusfeltétele lesz, a belső cklus cklusmagja pedg a megfelelő programág (j:=j+1). Az elágazás másk ága a belső cklus befejeződése után (tehát j>m esetben) hajtódk végre: először a külső cklus magjának végén az :=+1, majd a cklusmag smételt futtatásának elején, a belső cklus ndexváltozójának ncalzálása (j:=1) során történk meg. Természetesen ezt a két egymásba ágyazott cklust számlálásos cklusként érdemes leírn. A továbbakban m s többnyre ezt a változatot fogjuk használn. Meg lehet mutatn, hogy ez ekvvalens az előbb változattal. :=1.. n j :=1.. m F( a[,j] ) A szekvencáls nputfájl felsoroló típusa egy szekvencáls nputfájllal (f), az abból legutoljára kolvasott elem értékkel (df), és az utolsó olvasás státuszával (sf) reprezentál egy bejárót. A szekvencáls nputfájl felsorolása csak az elejétől végég történő olvasással lehetséges, ezért az sf, df, f : read művelet mplementálja a Frst() és a Next() műveleteket, a Current() a df értékét adja vssza, az End() pedg az sf=abnorm vzsgálat értékével azonos. Mnden művelet bármkor alkalmazható. 114
8.3. Nevezetes felsorolók sf, df, f : read sf=norm F(df) sf, df, f : read A Frst() műveletet az először kadott sf,df,f:read művelet váltja k. Ez az aktuáls elemet a df segédváltozóba tesz, így a Current() helyett közvetlenül a df értékét lehet használn. A read művelet mndg értelmezett, de a bejárást nem lehet smételten elndítan vele. Az smételten kadott sf,df,f:read művelet ugyans már az f.next() művelettel egyenértékű. Az f.end() az olvasás skerességét mutató sf státuszváltozó vzsgálatával helyettesíthető. (Mnd a négy művelet mnden esetben, a felsoroláson kívül s termnál.) A halmazt felsoroló típus reprezentácójában egy a felsorolandó elemeket tartalmazó h halmaz szerepel. Ha a h=, akkor a halmaz bejárása nem lehetséges vagy nem folytatható ez lesz tehát az End() művelet. Ha a h, akkor könnyen kválaszthatjuk felsorolás számára a halmaznak akár az első, akár soron következő elemét. Természetesen az elemek halmazbel sorrendjéről nem beszélhetünk, csak a felsorolás sorrendjéről. Ez az elemkválasztás elvégezhető a nem-determnsztkus e: h értékkválasztással éppen úgy, mnt a halmazokra korábban bevezetett determnsztkus elemkválasztás (e:=mem(h)). M ez utóbbt fogjuk a Current() művelet megvalósítására felhasználn azért, hogy amkor a halmaz bejárása során tovább akarunk majd lépn, akkor éppen ezt, a felsorolás során előbb kválasztott elemet tudjuk majd kvenn a halmazból. Ehhez pedg pontosan ugyanúgy kell tudnunk megsmételn az elemkválasztást. A Next() művelet ugyans nem lesz más, mnt a mem(h) elemnek a h halmazból való eltávolítása, azaz a h:=h {mem(h)}. Ennek az elemkvonásnak az mplementácója egyszerűbb, mnt egy tetszőleges elem kvonásáé, mert tt mndg csak olyan elemet veszünk el a h halmazból, amely bztosan szerepel benne, ezért ezt külön nem kell vzsgáln. A Next() művelet s (akárcsak a Current()) csak a bejárás alatt azaz amíg az End() hams alkalmazható. Láthatjuk azonban, hogy sem az End(), sem a Current(), sem a Next() művelet alkalmazásához nem kell semmlyen előkészületet tenn, azaz a felsorolást elndító Frst() művelet halmazok bejárása esetén az üres (semmt sem csnáló) program lesz. Ezen megjegyzéseknek megfelelően a halmaz elemenek feldolgozását az alább algortmus végz el: h F(mem(e)) h := h {mem(e)} 115
8. Programozás tételek felsoroló típusokra 8.4. Programozás tételek általánosítása A programozás tételenket korábban az egész számok ntervallumára fogalmaztuk meg, ahol egy ntervallumon értelmezett függvénynek értéket kellett feldolgozn. Ennek során bejártuk, felsoroltuk az ntervallum elemet, és mndg az aktuáls egész számhoz tartozó függvényértéket vzsgáltuk meg. Az egész számok ntervallumához mnt azt láttuk elkészíthető egy klasszkus sorrendű felsoroló, amely éppen úgy képes az ntervallumot bejárn, ahogy azt az ntervallumos programozás tételekben láttuk.. Ennek alapján általánosíthatjuk a programozás tételenket bármlyen más felsoroló típusra s. A feladatokat lyenkor nem ntervallumon értelmezett függvényekre, hanem egy felsorolóra, pontosabban a felsoroló által felsorolt értékeken értelmezett függvényekre mondjuk k. A megoldó programokban az ntervallumot befutó helyett a Current(), az :=m értékadás helyett a Frst(), az :=+1 értékadás helyett a Next(), és az n feltétel helyett az End() művelet használjuk. Az általános tételekre aztán bármelyk konkrét felsorolóra megfogalmazott összegzés, számlálás, maxmum vagy adott tulajdonságú elem keresése vsszavezethető. Ezek között természetesen az ntervallumon értelmezett függvényekkel megfogalmazott feladatok s, tehát mnden eddg megszerzett feladat-megoldás tapasztalatunk megmarad. A programozás tételek ehhez hasonló általánosításaval egyszer-egyszer már korábban s találkoztunk. Már korábban s oldottunk meg olyan feladatokat, ahol nem ntervallumon értelmezett függvényre, hanem vektorra alkalmaztuk a tételenket. Ezt eddg azzal magyaráztuk, hogy a vektor felfogható egy táblázattal megadott egész ntervallumon értelmezett függvénynek. Most már egy másk magyarázatunk s van. Az ntervallum és a vektor rokon típusok: mndkettőhöz készíthető felsoroló. Egy felsorolóra megfogalmazott összegzés, számlálás, maxmum vagy adott tulajdonságú elem keresés során pedg mndegy, hogy egy ntervallumon értelmezett függvény értéket kell-e sorban megvzsgáln vagy egy vektornak az elemet, hszen ezeket az értékeket úgys a felsorolás állítja elő. Az alábbakban bemutatott általános programozás tételekben nem közvetlenül a felsorolt elemeket dolgozzuk fel (adjuk össze, számoljuk meg, stb.), hanem az elemekhez hozzárendelt értékeket. Ezeket bzonyos tételeknél (pl. összegzés, maxmum kválasztás) egy f:e H függvény (ez sokszor lehet az denttás), másoknál (pl. számlálás, keresés) egy :E L logka függvény jelöl k. amely a felsorolt értékekhez rendel a feldolgozás alapját képező értékeket. Ennek következtében a feldolgozás során általában nem a t.current() értékeket, hanem az f(t.current()) vagy (t.current()) értékeket kell vzsgáln. A maxmum kválasztásnál, a feltételes maxmum keresésnél, a kválasztásnál és a lneárs keresésnél célszerű bevezetn egy-egy új specfkácós jelölést. Mnd a négy esetben megadjuk ugyan az utófeltétel eredet formuláját s, amelyről azonban látszk, hogy a vsszavezetés szempontjából sok lényegtelen elemet tartalmaz. Ezért ndokolt a rövdített jelölések használata. 116
8.4. Programozás tételek általánosítása Összegzés Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy f:e H függvény. A H halmazon értelmezzük az összeadás asszocatív, baloldal nullelemes műveletét. Határozzuk meg a függvénynek a t elemehez rendelt értékenek összegét, azaz a f ( t ) értékét! (Üres objektum esetén az összeg értéke defnícó szernt a nullelem: 0). Specfkácó: Algortmus: t kfejezés A = ( t:enor(e), s:n ) Ef = ( t=t ) Uf = (s = f ( t, ) ) t ' s := 0 t.frst() t.end() s := s + f(t.current()) t.next() Számlálás Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy :E L feltétel. Határozzuk meg, hogy a felsoroló objektum elem értéke közül hányra teljesül a feltétel. Specfkácó: A = ( t:enor(e), c:n ) Ef = ( t=t ) t' Uf = ( c 1) ( t, ) Algortmus: c:=c+1 c:=0 t.frst() t.end() ( t.current() ) t.next() SKIP 117
8. Programozás tételek felsoroló típusokra Maxmum kválasztás Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy f:e H függvény. A H halmazon defnáltunk egy teljes rendezés relácót. Feltesszük, hogy t nem üres. Határozzuk meg az f függvénynek a t elemehez rendelt értéke között a maxmálsat. Specfkácó: A = ( t:enor(e), max:h, elem:e ) Ef = ( t=t t >0 ) Uf = (max = f(elem) = t' = max f ( t, ) elem t ) = (max, elem = max f ( t, ) ) t' Feltételes maxmum keresés Algortmus: t.frst() max, elem:= f(t.current()), t.current() t.next() t.end() f(t.current())>max max, elem:= f(t.current()), t.current() t.next() SKIP Feladat: Adott egy enor(e) felsoroló típus t objektum és egy f:e H függvény. A H halmazon defnáltunk egy teljes rendezés relácót. Adott továbbá egy :H L feltétel. Határozzuk meg t azon elemehez rendelt f szernt értékek között a maxmálsat, amelyek kelégítk a feltételt. Specfkácó: A = ( t:enor(e), l:l, max:h, elem:e ) Ef = ( t=t ) Uf = ((l= [1.. t ]: (t ) ) (l elem t (elem) max=f(elem) Algortmus: [1.. t ]: ( (t ) f(t ) max) ) ) = (l, max, elem = max f ( t, ) ) l:= hams; t.frst() t.end() t' ' ( t ) (t.current()) (t.current()) l ( t.current()) l SKIP f(t.current())>max l, max, elem := max, elem:= f(t.current()), t.current() t.next() SKIP gaz, f(t.current()), t.current() 118
8.4. Programozás tételek általánosítása Kválasztás Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy :E L feltétel. Keressük a t bejárása során az első olyan elem értéket, amely kelégít a :E L feltételt, ha tudjuk, hogy bztosan van lyen. Specfkácó: A = ( t:enor(e), elem:e ) Ef = ( t=t [1.. t ]: (t ) ), Uf = ( [1.. t ]: elem (elem) j [1..-1]: ( t, ) t = t [+1.. t ]) )= ' = ( elem, t select ( t )) t Algortmus: t.frst() (t.current()) t.next() elem:=t.current() Lneárs keresés Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy :E L feltétel. Keressük a t bejárása során az első olyan elem értéket, amely kelégít a :E L feltételt. Specfkácó: A = ( t:enor(e), l:l, elem:e ) Ef = ( t=t ) Uf = ( (l= [1.. t ]: (t )), (l [1.. t ]: elem (elem) j [1..-1]: ( t, )) t = t [+1.. t ]) )= ' = ( l, elem, t search ( ) ) t' t t Algortmus: l := hams; t.frst() l t.end() elem := t.current() l := (elem) t.next() Rekurzív függvény helyettesítés értéke Feladat: Adott egy enor(e) felsoroló típusú t objektum és egy f:z H k-ad rendű 1 bázsú rekurzív függvény (k poztív egész szám) úgy, hogy f() = h(t, f( 1),...,f( k) ) ahol 1 és a f egy E H k H függvény, továbbá f(m 1)=e m 1,..., f(m k)= e m k, ahol e m-1,..., e m k H-bel értékek. Számítsuk k az f függvény t helyen felvett értékét! Specfkácó: A = ( t:enor(e), y:h ) Ef = ( t=t ) Uf = ( y=f( t ) ) Algortmus: y, y 1,..., y k 1 := e m 1, e m 2,..., e m k ; t.frst() y, y 1, y 2,..., y k 1 := t.end() h(t.current(), y, y 1,..., y k 1 ), y, y 1,..., y k 2 t.next() 119
8. Programozás tételek felsoroló típusokra A programozás tételek alkalmazásakor ha körültekntően járunk el szabad az algortmuson hatékonyságot javító módosításokat tenn. Ilyen például az, amkor ahelyett, hogy sokszor egymás után lekérdezzük a t.current() értékét, azt annak első lekérdezésénél egy segédváltozóba elmentjük. A maxmum-kválasztás lletve feltételes maxmum keresés esetén a feldolgozás eredménye között szerepel mnd a megtalált maxmáls érték, mnd az az elem, amelyhez a maxmáls érték tartozk. Konkrét esetekben azonban nncs mndg mndkettőre szükség. Indexelhető gyűjtemények (vektor, sorozat) esetén azonban az elem helyett elég megadn annak ndexét. Olyan esetekben, ahol a f függvény denttás, azaz egy elem és annak értéke megegyezk, a maxmáls elem és maxmáls érték közül elég csak az egyket nylvántartan az algortmusban. Kválasztásoknál nem kell a felsoroló által szolgáltatott értéksorozatnak végesnek lenne, hszen ez a tétel más módon garantálja a feldolgozás véges lépésben történő leállását. A lneárs keresésnél és kválasztásnál az eredmények között szerepel maga a felsoroló s. Ennek az oka az, hogy ennél a két tételnél korábban s leállhat a feldolgozás, mnt hogy a felsorolás véget érne, és ekkor maradnak még fel nem sorolt (fel nem dolgozott) elemek. Ezeket az elemeket tovább feldolgozásnak lehet alávetn, ha a felsorolót tovább használjuk. Felhívjuk azonban a fgyelmet arra, hogy egy már korábban használt felsorolóval dolgozunk tovább, akkor nem szabad a Frst() művelettel újrandítan a felsorolást. A specfkácóban egy lyen folytatólagos felsorolásra úgy fogunk utaln, hogy a feldolgozás szmbóluma alatt szereplő =1 helyére csak az -t írjuk. Nevezetes felsorolók esetén a specfkácós jelölés módosulhat úgy, hogy abban a felsorolót az őt reprezentáló elemekkel helyettesítjük. Például egy x szekvencáls nputfájlon folyó feldolgozásnál, ahol a fájl bejárását az sx, dx, x:read művelet végezzük, a felsorolót az sx, dx, x hármas reprezentálja. Amkor egy keresés vagy a kválasztás úgy áll le, hogy az sx=norm, akkor a dx mutatja az aktuáls, még fel nem dolgozott elemet, az x pedg az összes több feldolgozatlan elemet. Tehát a tovább feldolgozásra váró már megkezdett felsoroló, am a feldolgozás eredmény között szerepel, az sx, dx, x hármasa lesz. l, elem,sx,dx, x search ) lletve elem sx, dx, x select ( ) (, Egy h halmaz felsorolóját maga a h halmaz reprezentálja. A keresés vagy kválasztás esetén a feldolgozatlan elemek a halmazban maradnak: l,elem,h search ( e) lletve elem, h select ( e) e h ' Mnd a fájl, mnd a halmaz esetében a felsorolandó adat folyamatosan változk (elfogy) a bejárás során. Más a helyzet például egy vektor bejárásánál. Egy m..n ntervallumon értelmezett v vektor feldolgozása esetén maga a vektor nem változk (v=v ), csak a bejárás helyét jelző ndex. A feldolgozás eredményénél, ha szükségünk van arra, hogy hol állt le a felsorolás, elég ezt feltüntetn: v e h ' l,elem, search ( v[ ]) lletve elem, select ( v[ ]) m m 120
8.5. Vsszavezetés nevezetes felsorolókkal 8.5. Vsszavezetés nevezetes felsorolókkal A most bevezetett tételek segítségével egyszerűvé válk az olyan feladatok megoldása, amelyek felsorolt értékek összegzését, megszámolását, maxmum-kválasztását vagy keresését írják elő. Ha a feladat specfkácójában rámutatunk arra, hogy mlyen felsorolóra vonatkozk a feladat (hogyan kell mplementáln a Frst(), Next(), End() és Current() műveleteket) és mlyen programozás tétel alapján kell a felsorolt értékeket feldolgozn (m helyettesít az adott programozás tételben szereplő függvényt, függvényeket), akkor vsszavezetéssel megkaphatjuk a megoldást. Ráadásul ha az alkalmazott felsorolás a 8.3 alfejezetben részletesen tárgyalt nevezetes felsorolók egyke, akkor nagyon egyszerű lesz a dolgunk. Most ez utóbb esetre mutatunk néhány példát, amellyel azt kívánjuk llusztráln, hogy a fentekkel mlyen erős eszközt kaptunk a módszeres programtervezés számára. (Azon feladatok megoldásával, amelyek megoldásához egyed módon kell a felsorolót megtervezn, a következő fejezetben foglalkozunk.) Először nagyon egyszerű, a vsszavezetés technkáját bemutató feladatokat oldunk meg. Négy feladatban halmazok lletve szekvencáls nputfájl feldolgozásáról, és a számlálás, maxmum-kválasztás, keresés és kválasztás tételenek alkalmazásáról lesz szó. Utána két mátrxos feladat megoldása következk. A mátrx kétdmenzós bejárása érdekes tanulságokkal szolgál a maxmum-kválasztás és a lneárs keresés alkalmazása esetén. Végül az úgynevezett elemenként feldolgozásokra, az összegzés tételének specáls alkalmazásara mutatunk példákat. Itt az összegzés eredménye s egy gyűjtemény, az összeadás művelete e gyűjteménybe történő új elem beírása. 8.1. Példa. Egy halmaz egész számokat tartalmaz. Keressük meg a halmaz maxmáls elemét! A feladat specfkácójának felírása nem jelent problémát. Az utófeltételből látszk, hogy tt egy halmaz elemenek felsorolására épített maxmum-kválasztásról van szó. A = ( h:set(z), max:z ) Ef = ( h=h ) Uf = ( Ef max = max e ) e h Ebben a specfkácóban azonban nem jelenk még meg explct módon a felsoroló. Alakítsuk át úgy a specfkácót, hogy annak állapotterében a halmaz helyett, a halmaz elemet felsoroló objektum jelenjen meg. Ebben az átfogalmazásban már nem lényeges, hogy egy halmaz felsorolásáról van szó, hszen a feladatot egy általános felsorolóra fogalmazzuk meg: A = ( t:enor(z), max:z ) Ef = ( t=t ) Uf = ( Ef max = t' max t, ) Az általános maxmum-kválasztás programozás tételére való vsszavezetés most már formálsan történk: az általános specfkácó és az tt látható specfkácó néhány, jól 121
8. Programozás tételek felsoroló típusokra körülhatárolható ponton tér csak el. A felsoroló egész számokat állít elő, a feldolgozást végző függvény pedg az egész számokon értelmezett denttás. E = H = Z f(e) = e ( e Z) Az denttás matt a feldolgozásnak tt a konkrét esetben csak egy eredménye (max) van, hszen a megtalált maxmáls értékű elem és annak értéke egy és ugyanaz. Ennek megfelelően átalakítva alkalmazhatjuk az általános algortmust: t.frst() max:= t.current() t.next() t.end() t.current()>max max:=t.current() SKIP t.next() Ezek után foglalkozunk a felsoroló műveletekkel. A h halmaz felsorolása smert (lásd 8.3 alfejezet), így a műveletek mplementácója adott: Frst() ~ SKIP, End() ~ h=, Current() ~ mem(h), Next() ~ h:=h-{mem(h)}. Ezek alapján elkészíthető a feladatot megoldó program végső változata. Ebben egy segédváltozót vezettünk be annak érdekében, hogy ne kelljen a mem(h)-t ugyanarra a h-ra többször egymás után végrehajtan. max:= mem(h) h:=h-{max} h e:= mem(h) e>max max:= e h:=h-{e} SKIP A gyakorlatban természetesen nem kell ennyre részletesen dokumentáln a vsszavezetés lépéset. A fent gondolatmenetből elegendő csak a feladat eredet specfkácóját, a felsoroló típusának megnevezését (tt: halmaz felsoroló), a vsszavezetés analógáját (tt: maxmum-kválasztás és f(e )~ e), és végül a végleges algortmust leírn. 122
8.5. Vsszavezetés nevezetes felsorolókkal 8.2. Példa. Számoljuk meg egy halmazbel szavak között, hogy hány a betűvel kezdődő van! A feladat specfkácója egyszerű: A = ( h:set(k*), db:n ) Ef = ( h=h ) Uf = ( db 1) szó h ' szó1 ' a' Ez vsszavezethető a számlálás programozás tételére halmaz felsorolóval, ahol a számlálás feltétele az aktuáls elem (szó) első betűjének megfelelő vzsgálata. A mem(h) (aktuáls szó) értékének deglenes tárolására a szó segédváltozót vezetjük be. db:= 0 h szó:= mem(h) szó 1 = a db:= db+1 h:=h-{szó} SKIP 8.3. Példa. Egy szekvencáls nputfájl egész számokat tartalmaz. Keressük meg az első nem-negatív elemét! A = (f:nfle(z), l:l, e:z) Ef = ( f=f ) f ' Uf = ( l, e search ( f ' 0) ) A feladatot szekvencáls nputfájl bejárása felett általános lneárs keresésre vezetjük vssza, ahol a keresett feltétel a vzsgált fájlbel elem nem-negatív tulajdonsága. A 8.3 alfejezetből tudjuk, hogy szekvencáls nputfájlok esetén a felsorolót az sf, df, f értékhármas reprezentálja, ahol sf az f-re vonatkozó olvasás státusza, df a kolvasott elem. A felsoroló Frst() és Next() műveletet az sf, df, f : read mplementálja, a Current() kfejezést a df, a End()-et pedg az sf=norm. l:=hams; sf, df, f : read l sf=norm e:= df l:= df 0 sf, df, f : read 123
8. Programozás tételek felsoroló típusokra 8.4. Példa. Keressük meg egy szekvencáls nputfájlban található szöveg első szavának kezdetét, azaz lépjük át (olvassuk k) egy szöveg elején levő szóközöket, és ha van nemszóköz s benne, akkor az első lyen kolvasása után álljunk meg! Ezt a feladatot a kválasztás programozás tételére vezethetjük vssza, ugyans vagy az első nem szóköz karaktert kell megtalálnunk, vagy ha lyen nncs, akkor a fájl végét; ez az összetett feltétel bztosan teljesül. Az eredmény tehát egyrészt az sf és df, másrészt a megolvasott szekvencáls nputfájl lesz, azaz sf, df, f. A specfkácó meglehetősen körülményes a megoldó algortmushoz képest. A = (f:nfle(k), df: K, sf:státusz) Ef = ( f=f ) Uf = ( sf, df, f select ( f ' f ' ' ') ) sf, df, f : read sf=norm df= sf, df, f : read Kétdmenzós jellegénél fogva a mátrxokra értelmezett maxmum-kválasztás és lneárs keresés s érdekes tanulságokkal jár. 8.5. Példa. Adjuk meg egy egész elemű mátrx egyk maxmáls elemét és annak ndexet! Specfkáljuk először a feladatot! Feltesszük, hogy a mátrxnak n darab sora és m darab oszlopa van, és mndkettő értéke legalább egy (ez utóbb kell ahhoz, hogy a maxmum kválasztás értelmes legyen). A = (a:z n m, max:z, nd:n, jnd:n)) Ef = ( a=a n>0 m>0 ) Uf = ( a=a nd [1.. n] jnd [1.. m] max = a[nd,jnd] = max a[, j] ) = n, m = (a=a max, nd, jnd = max a[, j] ), j 1 n, m, j 1 A specfkácóból látható, hogy tt a mátrx elemet kell bejárn és egyk maxmáls elemét megtaláln. Úgy gondoljuk, nem szorul külön magyarázatra a specfkácóban használt, csak mátrxokra alkalmazható dupla ndexváltozós maxmum-kválasztásnak a jele. Ha ötvözzük a felsoroló típusra megfogalmazott maxmum kválasztás algortmusát azzal, hogyan lehet a Frst(), Next(), End() és Current() műveleteket mátrxok elemenek bejárásánál mplementáln (ezt a 8.3. alfejezetben többféleképpen s megoldottuk), akkor megkapjuk a feladat megoldását. Mvel a legsmertebb a két leszámlálásos cklusra írt változat, ezért m s ezt adjuk meg. Ez a változat rendelkezk egy apró hbával az a[1,1]-et kétszer s megvzsgálja: először a kezdet értékek beállításánál, majd a cklusmag első lefutása során. Ezt a hbát ebben a változatban csak úgy lehetne korrgáln, ha a cklusmagba beletennénk egy plusz elágazást, 124
8.5. Vsszavezetés nevezetes felsorolókkal amely =1 és j=1 esetben megtltaná az összehasonlítást, de ettől a program hatékonysága rosszabb lenne a jelenleg változatnál. nd, jnd, max:=1, 1, a[1,1] :=1.. n j :=1.. m a[,j]>max nd, jnd, max:=, j, a[,j] SKIP Megjegyezzük, hogy egy a mátrx elemere közvetlenül megfogalmazott feladatot mnt amlyen ez s át lehetne fogalmazn úgy, hogy az a mátrx sorara vonatkozzon. Például a maxmáls értéket kereshetjük a mátrx soranak maxmáls eleme között. Ekkor a feladat egymásba ágyazott ntervallumos programozás tételekre s vsszavezethető. 8.6. Példa. Keressünk egy egész elemű mátrxban egy páros számot és adjuk meg annak ndexet! A = (a:z n m, l:l, nd:n, jnd:n)) Ef = ( a=a ) Uf = ( a=a (l = [1.. n] j [1.. m]: 2 a[nd,jnd]) (l nd [1.. n] jnd [1.. m] 2 a[nd,jnd])) ) n, m = ( a=a ( l, nd, jnd search (2 a[, j] ) ),j 1 Itt s bevezettünk egy specáls mátrxos jelölést a lneárs keresésre. Ezzel egyrészt a kétdmenzós bejárásra utalunk, másrészt arra, hogy a keresésnek három eredménye van: egy logka érték, és annak gaz értéke esetén a megtalált elem sor- és oszlopndexe. Ehhez lleszkedve a megoldásnak s a két-cklusos változatát adjuk meg. l, :=hams, 1 l n j:=1 l j m l, nd, jnd := 2 a[,j],, j j:=j+1 :=+1 Egy felsoroló típusú adat feldolgozásának egy fontos csoportja az, amkor a kmenő adat s elem értékek gyűjteménye (például halmaz, sorozat, vektor vagy szekvencáls outputfájl). Amkor a bemenő felsoroló adat aktuáls elemét feldolgozzuk, e feldolgozás eredményeként 125
8. Programozás tételek felsoroló típusokra kapott új elemmel vagy elemekkel kbővítjük a kmenő adatot (halmazhoz hozzáunózunk, sorozathoz hozzáfűzünk, stb.). Mvel a kmenő adat megváltoztatása (az unó, hozzáfűzés, stb.) egy baloldal egységelemes asszocatív művelet, ezen feladatok megoldását az összegzés programozás tételére vezethetjük vssza. Ha egy lyen feladatra még az s gaz, hogy a bemenő adat egy elemének feldolgozása nem függ más tényezőtől (nem függ a bemenő adat több elemétől vagy a kmenő adattól) csak magától a feldolgozandó elemtől, azaz a bemenő adat feldolgozása annak felsorolására épül, akkor a feladatot elemenként feldolgozhatónak, a megoldását elemenként feldolgozásnak nevezzük. (Létezk az elemenként feldolgozásnak egy ennél még szgorúbb értelmezése s, amely szernt az eddg felsorolt feltételeken túl annak s teljesülne kell, hogy egy-egy elem feldolgozásának eredménye a kmenő adatot több elemétől se függjön, azaz az eredménnyel a kmenő adatot annak több elemétől függetlenül lehessen bővíten.) Az elemenként feldolgozható feladat-osztályhoz tartozk az adatfeldolgozás számos alapfeladata: a másolás, a kválogatás, a szétválogatás, stb. Oldjunk meg először két szgorúan elemenként feldolgozható feladatot, majd egy nem szgorú elemenként feldolgozásra, utána egy nem elemenként feldolgozásra s mutatunk példát. Utoljára egy olyan elemenként feldolgozásról lesz szó, ahol egy bemenet gyűjteményből három kmenet gyűjteményt kell egyszerre előállítan. 8.7. Példa. Másoljunk át egy karaktereket tartalmazó szekvencáls nputfájlt egy outputfájlba úgy, hogy mnden karaktert megduplázunk! A = (x:nfle(k), y:outfle(k)) Ef = ( x=x ) Uf = ( y, ) Az utófeltételben használt művelet az összefűzés (konkatenácó) jele. Ez egy asszocatív művelet, amelynek az üres sorozat az egységeleme. Ezért a feladat az összegzés programozás tételére vsszavezethető úgy, hogy a feldolgozás egy szekvencáls nputfájlra, annak nevezetes bejárására épül. A szekvencáls outputfájlhoz a wrte művelettel lehet hozzáfűzn egy újabb sorozatot, azaz egy y := y <dx, dx> értékadást az y:wrte(<dx, dx>) mplementál. y:=<> sx,dx,x:read sx = norm y:wrte(<dx, dx>) sx,dx,x:read Látjuk, hogy az elemenként feldolgozható feladatok egy bemenő adat-elemhez nem feltétlenül csak egy kmenő adat-elemet állítanak elő, ugyanakkor nagyon gyakorak azok a feladatok (lyen a következő), amelyek a bemenő adat egy eleméhez vagy egy új elemet, vagy semmt sem rendelnek. 126
8.5. Vsszavezetés nevezetes felsorolókkal 8.8. Példa. Válogassuk k egy egész számokat tartalmazó halmazból a páros számokat, és helyezzük el őket egy másk halmazba! A feladat egy halmaz bejárásán értelmezett összegzés, ahol a feldolgozás f:z 2 Z függvénye egy páros számhoz a számot tartalmazó egy elemű halmazt, páratlan számhoz az üres halmazt rendel. A = (x:set(z), y:set(z)) Ef = ( x=x ) Uf = ( y { e} ) U e x ' e páros A programban szereplő y:=y f(e) értékadást egy elágazás valósítja meg. y:= x e:=mem(h) e páros y:=y {e} SKIP x:=x {e} Megjegyezzük, hogy a szgorú elemenként feldolgozás következtében az y:=y {e}értékadás mplementácója tt egyszerűbb, mnt általában. Ugyans bztosak lehetünk abban, hogy az e elem még nncs az y halmazban, ezért ezt nem s kell külön vzsgáln. Az elem-unó műveletének tehát ugyanúgy az egyszerűsített változatával dolgozhatunk tt, mnt az x:=x {e} elemkvonás esetében, ahol meg azt nem kell ellenőrzn, hogy az e elem tényleg benne van-e az x halmazban. 8.9. Példa. Másoljuk át egy egész számokat tartalmazó vektorból egy halmazba az elemek 7-tel vett osztás maradékat! A feladat egy vektor szokásos bejárásán értelmezett összegzés, ahol a feldolgozás függvénye az adott elemnek 7-tel vett osztás maradékát állítja elő, amelyet az eredmény halmazhoz kell unózn. Ennek a lépésnek a hatása nem független az eredmény halmaz tartalmától, hszen ha az újonnan hozzáadandó maradék már szerepel a halmazban, akkor a halmaz nem fog megváltozn. Ez a feladat tehát egy nem szgorú értelemben vett elemenként feldolgozás. 127
8. Programozás tételek felsoroló típusokra A = (x: Z n, y:set(z)) Ef = ( x=x ) U n Uf = ( y { x mod 7} ) y:= := 1.. n y:=y {x mod 7} Látható, hogy az y:=y {x mod 7}művelet eredménye az y korább tartalmától függ: egy halmazban egy elem csak egyszer szerepelhet, ezért ha az x szám osztás maradéka már szerepel y-ban, akkor a művelet nem változtatja meg y-t. Az művelet ezért tt nem megkülönböztetett, mnt az előző megoldásnál. (Érdekes vszont, hogy ha ugyanez a feladat az eredményt egy sorozatba tenné, akkor már szgorúan elemenként feldolgozhatóvá válna, hszen sorozatoknál megengedjük, hogy abban ugyanaz az érték többször s előfordulhasson, így ugyanazt a maradékot többször s elhelyezhetjük.) Nem elemenként feldolgozható a következő feladat, ahol a feldolgozás alapjául szolgáló szekvencáls nputfájlból egy rekurzív függvény által előállított értékeket fűzünk egy outputfájlba. A rekurzív függvény értéke ugyans természeténél fogva az előző értékere támaszkodk. A megoldás előállítása vszont nem különbözk az előző feladatok megoldásatól. 8.10. Példa. Másoljuk át a karaktereket egy szekvencáls nputfájlból egy outputfájlba úgy, hogy ott, ahol több szóköz követte egymást, csak egyetlen szóközt tartunk meg! A = (x:nfle(k), y:outfle(k)) Ef = ( x=x ) Uf = ( y f1( ) ) = ( y f ( 1 ) ) Az utófeltételben használt f 1 függvény az x sorozat -edk karakterét változtatás nélkül adja vssza, ha az nem szóköz, vagy olyan szóköz, am közvetlenül egy nem-szóköz után áll. Ez a függvény az egyk komponense az f:[0.. x K* L rekurzív függvénynek: [ 1.. ]: (, hams) f ( ) ( ' ', gaz) (, gaz) f ( 0) (, hams) ha ha ha ( 1) A rekurzív függvényt közvetlenül a szekvencáls nputfájlból olvasott elemre s megfogalmazhatjuk: megmutatja, mlyen sorozattá kell azt az elemet átalakítan. A megoldást egy olyan összegzésre (konkatenácóra) vezetjük vssza, ahol egy szekvencáls nputfájl elemet kell felsoroln, ezen elemeket egy rekurzív függvénnyel sorozattá alakítan, majd a ' ' ' f ' ' f 2 ' 2 ( 1) 128
8.5. Vsszavezetés nevezetes felsorolókkal sorozatot az outputfájlba befűzn. A cklusmagban megjelenő rekurzív függvényt a korábban (6.2 alfejezet) látott technkával bontjuk k. y, l:=<>, hams sx,dx,x:read sx = norm dx dx= l dx= l y:wrte( ) y:wrte(dx) SKIP l:= dx= sx,dx,x:read A következő példa újra egy szgorúan elemenként feldolgozható feladat lesz, amelynek az a sajátossága, hogy ugyanazon a gyűjteményen több, egymástól független feldolgozást kell elvégezn. Az lyen feladat teljesen önálló részfeladatokra bontható, majd a részmegoldásokat a közös gyűjtemény bejárására egyetlen cklusba lehet összevonn. 8.11. Példa. Válogassunk szét egy szekvencáls nputfájlban rögzített bűnügy nylvántartásból egy sorozatba, egy outputfájlba, egy halmazba és egy vektorba a gyanúsítottakat aszernt, hogy az llető 180 cm-nél magasabb-e és barna hajú, vagy fekete hajú és 60 kg-nál könnyebb, vagy fekete hajú és nncs albje! Az állapottérnek egy szekvencáls nputfájl a bemenő adata, a kmenő adata pedg egy szekvencáls outputfájl, egy halmaz és egy vektor. Az állapottér a vektorról csak annyt mond, hogy az egy kellően hosszú véges sorozat, és majd az utófeltétel fogja a vektor első n elemét jellemezn. A specfkácóban jól elkülönül a feladat három önállóan s megoldható része. A = (x:nfle(ember), y: outfle(ember), z: set(ember), v: Ember*, n:z)) Ember=rec(név:K *,mag:n;súly:n,haj: K *,alb:l) Ef = ( x=x ) Uf = ( y. mag 180. haj ' barna' y:=<> sx,dx,x:read x U ' {. súly 60. haj ' fekete ' z } v [1.. n] ) z:= sx,dx,x:read. haj ' fekete '. alb n:=0 sx,dx,x:read sx = norm sx = norm sx = norm dx.mag>180 dx.haj= barna dx.súly<60 dx.haj= fekete dx.haj= fekete dx.alb y :wrte(dx) SKIP z:=z {dx} SKIP v[++n]:=dx SKIP sx,dx,x:read sx,dx,x:read sx,dx,x:read 129