10. hét Érettségi feladatok megoldása
Tartalom:
10.1. Foci
10.2. Robot
10.3. Tesztkérdések
10.4. Feladatok
Az alábbiakban két emelt szintű érettségi feladatot
fogunk megoldani. Az eddig tanultak elegendő nyelvi eszközt biztosítanak minden
részfeladat megoldásához. Nagyban megkönnyíti a munkánkat az a fontos körülmény,
hogy sem az inputállományok, sem a felhasználó által bevitt adatok helyességét
ill. érvényességét nem kell ellenőrizni. Így lényegesen csökken a kódolási idő,
de meg kell jegyezni, hogy a valóságban az ilyen programok használhatósága
nagyban korlátozott.
A feladatok megoldása fájlműveleteket is igényel, amelyek metódusai megkövetelik a kivételkezelést, ezért a programok main metódusának fejében a throws IOException záradékkal a kivételeket "továbbdobjuk" a Java futtató rendszere felé.
Első érettségi feladatunk lényege a következő: egy labdarúgó-bajnokság mérkőzéseinek adatait tartalmazó fájlt kell feldolgoznunk és a megadott kérdésekre ezen adatok alapján válaszolnunk.
Mint majd látjuk, a program sok metódusa igényli a java.io és a java.util osztályok alosztályait, amelyek alapértelmezésben nincsenek implementálva, ezért a program elején importálnunk kell őket:

A feladat eredeti szövegezése: foci.pdf. A forrásállomány: meccs.txt.
1. feladat:
Olvassuk be a meccs.txt fájlban található adatokat! Tárolásukra két tömböt fogunk használni, mivel az adatok egy része egész számokat, másik része szöveget tartalmaz. Alkalmazzuk a "tömb a tömbben" módszert, mivel adategységenként (az inputfájl egy-egy sora) 5 szám- és 2 szövegadat tárolásáról kell gondoskodnunk. A tömböket statikus osztályváltozókként definiáljuk, mivel a későbbiekben egy saját metódussal - MaxMin()- fogunk hivatkozni rájuk.

A megoldás a mérkőzések számának beolvasásával kezdődik, majd soronként feldolgozzuk az inputfájlt. Ez a StringTokenizer osztály metódusainak segítségével (a sort a szóközök mentén feldarabolva) könnyen megvalósítható. Figyeljük meg, hogy a tömbök indexelése 0-tól kezdődik!

A nextToken() metódus stringet ad vissza, ezért a számoknál a konvertáláshoz igénybe kell venni az Integer osztály parseInt() metódusát.
2. feladat:
Egy forduló sorszámát bekérve írjuk ki a képernyőre az adott forduló mérkőzéseinek adatait a megadott formában.

A billentyűzet inputhoz jól használható a Scanner osztály. A kiírás formátumának beállítása aprólékos munkát igényel, de szerencsére a System.out.println() metódus támogatja a szám- ill. szövegváltozók vegyes használatát.
3. feladat:
Azokat a csapatokat (és a forduló sorszámát) kell kiíratni a képernyőre, amelyek megfordították a mérkőzés állását, tehát a félidőben még vesztésre állva a mérkőzést a végén megnyerték.

Az összetett feltételvizsgálat oka az, hogy mind a hazai-, mind a vendégcsapat szempontjából meg kell vizsgálnunk a mérkőzések félidei- és végeredményét.
4. feladat:
Egy csapat nevét kell bekérni a felhasználótól, majd (ez esetben egy csn változóban) eltárolva felhasználni a következő feladatok megoldásához.

5. feladat:
Az adott csapat által lőtt és kapott gólokat kell összesítve megjeleníteni, tehát a lekérdezés szűrőfeltétele a csapat neve. Ne feledjük, hogy egy csapat hazaiként és vendégként is szerepelhet egy mérkőzésen, és csak a mérkőzések végeredményét kell összesíteni!

Figyeljük meg a string matches() metódusát! Nagyon jól használható összehasonlításokhoz.
6. feladat:
Feladatunk az adott csapatról meghatározni, hogy otthon melyik fordulóban kapott ki először, és ki volt a legyőzője. Azt is jelezni kell, ha veretlen maradt.

A mérkőzések adatait tartalmazó tömbök feldolgozását végző while ciklus csak addig fut, amíg nem talál egy otthoni vereséget az adott csapatnál. Ezt a kikapott nevű logikai változó használatával érjük el.
7. feladat:
Statisztikát kell készítenünk a bajnokságban előforduló végeredményekről, amelyet egy fájlban kell eltárolnunk. Fontos feltétel, hogy a fordított eredményeket egyezőknek kell tekinteni és a nagyobb számot mindig előre kell írni.
A feladat megoldásához érdemes írni egy saját metódust, amely a végeredmények gólszámából egy olyan számot állít elő, amelynek tízes helyiértékét a nagyobb-, egyes helyiértékét pedig a kisebb gólszámok adják. Így a végeredményeket egységesítve sokkal könnyebb a feldolgozásuk.

A végeredmények számának tárolására a
T1 tömb
egy üres oszlopa szolgál. A tömb az összes végeredmény-fajtát tartalmazza, tehát
külön adatstruktúrát nem szükséges létrehozni hozzájuk. Az algoritmus lényege
az, hogy az egyes végeredményeket - függetlenül attól, hogy milyen
sorrendűek (pl. 2-1 vagy 1-2) - az első eredmény-előfordulás helyén tárolva
számláljuk meg. A kimeneti fájl létrehozásához kiválóan alkalmas az egyszerűen
használható PrintWriter
osztály.

Figyeljük meg a második - beágyazott - for ciklus felépítését, amely az eredmények eltárolását végzi! Mindig csak addig fut - a tömb elejétől kezdve a vizsgálatot -, amíg nem talál egy olyan végeredményt, mint amilyet el akar tárolni. A kiíratásnál arra kell figyelni, hogy a teljes tömböt fel kell dolgozni, mivel egy eredmény akár a tömb utolsó sorában is szerepelhet! A gólszámok sorrendhelyes megjelenítéséhez a Math osztály max() és min() függvényei hathatós segítséget nyújtanak.
Összességében a feladatról elmondható, hogy - bár egyszerű fájlkezelő műveleteket igényel, de - néhány részfeladata magabiztos algoritmizáló képességet feltételez, és az adatok tárolásához a tömb adatstruktúra alapos ismerete feltétlenül szükséges.
A teljes program forráskódja: Foci.java.
Második feladatunk egy kis robot programozásáról szól. Négy irányba tud mozogni az egybetűs - égtájakat, mint irányokat jelölő - E, D, K és N parancsok hatására. Feladataink az ilyen utasításokból álló programsorok feldolgozásával kapcsolatosak.
Most is szükségünk van az io és util osztályokra, valamint egy számérték formázott megjelenítése miatt a DecimalFormat osztályra is:

A feladat eredeti szövegezése: robot.pdf. A forrásállomány: program.txt.
1. feladat:
Első lépésként olvassuk be a Program.txt állományt! Első sora a programok számát tartalmazza, a többi sor pedig egy-egy programot. Utóbbiakat egy egyszerű string típusú P tömbben tároljuk.

2/a. feladat:
Az összetett feladat egy utasítássor számának bekérésével kezdődik, majd el kell döntenünk az adott sorról, hogy egyszerűsíthető-e. Akkor egyszerűsíthető, ha közvetlenül egymás után két olyan lépés van előírva, amelynek eredményeképpen a robot egy helyben marad (pl. ED, NK). A kérdéses betűpárok megtalálásához a string contains() metódusát használjuk fel, amely igaz értéket ad, ha betűpárok bárhol előfordulnak a vizsgált stringben.

2/b. és 2/c. feladat:
Ezt a két részfeladatot érdemes egyszerre megoldanunk, mert mindkettő ugyanazt a ciklust igényli az utasítások feldolgozása során, így futási időt takaríthatunk meg.
A b. feladatban azt kell meghatároznunk, hogy a kiválasztott utasítássor végrehajtása után legkevesebb hány lépésben lehetne a robotot a kiindulási helyére visszajuttatni. Ehhez érdemes a lépéseket "lejátszani" úgy, hogy közben külön számoljuk a két tengelyen (észak-dél ill. kelet-nyugat) megtett lépéseket (ed és kn változók). Az egyik lépést pozitív, az ellentétes irányú párját negatív értékkel vesszük figyelembe. A végpozíciót elfoglalva az összesített lépésszámok abszolút értéke adja a megoldást.
A c. pont arra kíváncsi, hogy hány lépés után kerülünk a kiindulási ponttól a legtávolabbra, és ekkor mennyi ez a távolság. Utóbbi meghatározásához a Pitagorasz-tételt használjuk fel, és az eredményt valós számtípusú változóban tároljuk.

Figyeljük meg, hogy a négyzetgyökvonó- és a hatványozó függvények a Math osztály metódusai! Az eredmény kiírásánál formázott megjelenítést kell alkalmaznunk, amelyet a DecimalFormat osztály format() metódusa biztosít. A maszk # és 0 jele egy-egy számjegyet jelöl, és az utóbbi kötelező megjelenítést ír elő. Így lehet 3 tizedes pontosságú számot kiíratni.

3. feladat:
A kis robot tevékenységei (elindulás, lépés, irányváltás) különféle mértékű energiafelhasználással járnak. Meg kell határoznunk, hogy mely programok végrehajtása igényel maximum 100 egységnyi energiafelhasználást.

Figyeljük meg, hogy minden program rendelkezik egy fix energiaigénnyel, ami az indulásból és a megtett lépések számából adódik. Ezeken kívül már csak az irányváltásokat kell figyelni, amit két szomszédos utasítás különbözőségéből könnyen meghatározhatunk. Az utasítássor egy-egy elemét a String osztály charAt() metódusával könnyen meghatározhatjuk. Mivel mindig az aktuális utasítás kódját hasonlítjuk a sorban következőhöz, így a feldolgozó ciklus csak az utolsó előtti elemig fut!
4. feladat:
Új formátumúra kell alakítanunk és egy fájlba kiírnunk az utasítássorokat. A konverzió lényege az, hogy az egymás után ismétlődő parancsokat azok számával jelölve rövidítsünk az utasítássoron. Pl. az EEEDKNNNN sorból állítsuk elő a 3EDK4N (hivatalos nevén futáshossz-tömörítésű) utasítássort. Az egyedüli utasításokat változatlanul kell leírni.

A kimeneti fájl létrehozására a PrintWriter osztály most is kézenfekvő megoldás. Az átkódolás menete viszont már nem olyan egyszerű! Hozzunk létre minden utasítássorból egy "technikai sort", amely egy * karakterrel való kibővítéssel jön létre. Ez ahhoz kell, hogy a következő - a vizsgálatot a 2. karaktertől indító! - ciklus az adott karaktert az előzőhöz hasonlítva megszámlálhassa (az usz változóban) az egymás után következő azonos utasításkaraktereket.
5. feladat:
A cél egy új formátumú utasítás visszaalakítása a régi formátumra. A visszakódolandó utasítássort a felhasználótól kérjük be, amelyet az uts változóban helyezünk el. Figyelnünk kell arra is, hogy az ismétlődések száma maximum 200 lehet, azaz akár 3 számjegyből is állhat! Ez a körülmény alapvetően meghatározza az átalakító algoritmus felépítését, ugyanis az ismétlődésszámot adó karaktereket is gyűjtenünk kell (ism_kar változó).

Figyeljük meg, hogy az utasítássor elemeinek "darabolásához" a charAt() helyett a substring() metódust használjuk, ugyanis az utóbbinak van egy olyan matches() metódusa, amely segítségével nagyon tömör kóddal eldönthetjük, hogy az adott karakter utasításkód-e. A maszkban szereplő | jel a logikai vagy megfelelője. A szöveg-szám konverzióhoz most is az Integer osztály parseInt() metódusát használjuk. Abban az esetben, ha az ismétlődések száma nulla, akkor gondoskodnunk kell arról, hogy 1 legyen, mivel a kiíró programrész ciklusának legalább egyszer le kell futnia az utasításkarakter megjelenítéséhez. A 200-as határ vizsgálatához gyors, kényelmes és elegáns megoldást kínál a Math osztály min() függvénye.
A teljes program forráskódja: Robot.java.
10.3. Tesztkérdések
10.4.1. Jelenítsük meg a képernyőn vesszővel elválasztva a Fibonacci számok első 20 elemét ciklussal és a 21-30. elemét rekurzióval! (A Fibonacci-számok első két eleme a 0 és az 1, a következő elemek pedig az előző elemek összege, tehát 0, 1, 1, 2, 3, 5, 8, ...)
10.4.2. Készítsük el a Vigenere táblát! Ez a 26*26-os tábla az angol abc betűit tartalmazza a következő elrendezésben:

10.4.3. Kérjünk be a felhasználótól egy magyar nyelvű (ékezetes betűket is tartalmazó) szöveget! A beolvasott szöveget kódoljuk át az alábbiak szerint, majd írjuk ki a képernyőre:
a magyar ékezetes betűket "ékezetmentesíteni" kell (pl. á - a, ű - u)
csak az angol abc 26 betűje és a számjegyek szerepelhetnek a kódolt szövegben, szóköz vagy írásjelek nem
minden átalakított betű nagybetűs legyen
10.4.4. A szavak.txt szöveges fájl első sora tartalmazza a fájlban lévő szavak számát. Olvassuk be az összes szót, majd írjuk ki a képernyőre a legrövidebb ill. a leghosszabb szavak listáját! Készítsünk statisztikát a szavak hosszúságának gyakoriságáról!