Címkefelhő generálása

szófelhő logó

szófelhő logóA címkefelhők/szófelhők népszerűek, sok weboldalon megtalálhatóak. A CMS rendszerekben beépített szolgáltatás is lehet, vagy külön bővítmény/plugin is megvalósíthatja. Egy szövegben előforduló szavakból a gyakrabban előfordulókat nagyobb betűmérettel emeli ki. Eredménye lehet listás, táblázatos, esetleg képpé generált is. Kétféleképpen is megközelíthető, erre utal a Word Cloud és a Tag Cloud elnevezés. Utóbbi inkább egy blog taxonomiájához kapcsolódik és kategóriákra/címkékre érvényesül. A szakmai blogunkhoz is tartozik egy táblázatos címkefelhő. A szófelhő a szöveg betűméretén túl megjelenítheti a szavak előfordulását, például Java forráskód (63), címkefelhő (2).

Példánkban tetszőleges szöveget dolgozunk fel. Ebből felépítünk egy előfordulást is mutató listás szófelhőt, amely rendezett, és a szavak betűmérete 32-16-ig változik. Azok a szavak kerülnek a szófelhőbe, amelyek legalább 5-ször előfordulnak. Kezelünk kivételeket is, például olyan szavakat, amiket nem érdemes szófelhőbe tenni. Lépésenként haladva ismertetjük a megvalósító forráskódot, és külön megjeleníthetők az egyes lépések részeredményei.

A Java programozási nyelv csomagjait, osztályait, interfészeit, metódusait, műveleteit használjuk. Különböző adatszerkezetek kerülnek elő: tömb, generikus lista, generikus map, generikus folyam. Építünk a Stream API szolgáltatásaira és a lambda kifejezésekre. A megvalósítás könnyen testre szabható, kezeli a tipikusan előforduló igényeket.

1. Szövegforrás előkészítése

Generálunk egy 10 bekezdésből álló szöveget a Lorem Ipsum – All the facts – Lipsum generator weboldalon és a későbbi feldolgozáshoz mentjük a Java projekt files mappájába  lorem.txt néven. A fájl mérete: 5781 bájt. Szövegfájl:

2. Szöveges tartalom előkészítése

A megadott útvonalról a java.nio csomag metódusaival betöltjük a szövegfájl tartalmát byte[]-be, majd az s szövegbe. A replace() metódus hívásaival eltávolítjuk a szövegből a sor és bekezdés végét jelző soremelés ( LF="\n") és kocsi vissza ( CR="\r") vezérlőkaraktereket, a vessző és a pont írásjeleket (mindet külön-külön cseréljük a semmire), végül kisbetűssé alakítjuk ( toLowerCase()) a szöveget. A szöveg 5563 db karakterből áll. Előkészített szöveg:

3. Szólista elkészítése

A szóközök mentén darabolva ( split()) a szöveget elkészül belőle egy névtelen szövegtömb ( String[]), amit rögtön átalakítunk ( Arrays.asList()) szöveg típusú generikus listává ( List<String>). A lista 826 db elemből áll. Generikus lista:

4. Csoportosítás és megszámolás

A szólistát csoportosítjuk és megszámoljuk, hogy az egyes szavak hányszor fordulnak elő (másképpen: egy-egy csoport hány elemű). Elkészül a wordCountMap generikus map, amely kulcs-érték párok halmaza (leképezés). A kulcs a szó ( String), az érték a darabszáma ( Long). Alkalmazkodunk ahhoz, hogy a csoportosítás során használt counting() megszámoló művelet Long típusú értéket ad vissza. 188 db kulcs-érték párt kapunk. Generikus map:

5. Szűrés és rendezés

A generikus map-et kétszer szűrjük ( filter() művelet) úgy, hogy a kivételeket tartalmazó exceptList-ben ne szerepeljen a szó, valamint csak a legalább 5-ször előforduló szavakat hagyjuk meg. 71 db elemből álló folyam marad. Ebből a maradékból készítünk rendezett generikus folyamot ( sortedWordCountStream). A sorted() művelet két kulcs-érték párt hasonlít össze. A rendezés érték/darabszám szerint ( getValue()) csökkenő, azon belül kulcs/szavak szerint ( getKey()) növekvő sorrendet biztosít. Másképpen: ha az értékek megegyeznek, akkor a növekvő sorrendet a szavak ábécé sorrendje határozza meg, egyébként a darabszámok csökkenő sorrendje dönti el. Most már könnyen látható, hogy a leggyakrabban előforduló kevés szóból 15 van, 14 előfordulás nincs… Rendezett generikus folyam:

6. Saját típusú listává konvertálás

Definiálunk egy WordCount POJO-t, String típusú word nevű, Long típusú count nevű, int típusú fontSize nevű tulajdonságokkal, getter/setter metódusokkal, és toString() függvénnyel.

A map() intermediate művelettel a rendezett generikus folyamot bejárva, előállítjuk a POJO/ WordCount  típusú kimeneti objektumok rendezett generikus listáját. Továbbra is 71 elemmel dolgozunk. Rendezett generikus lista:

7. Darabszámok összegyűjtése

A POJO típusú rendezett generikus listában lévő objektumoktól elkért darabszámok ( getCount() POJO függvény) közül a különbözőeket ( distinct() művelet) összegyűjtjük egy Long típusú generikus listába ( distinctCountList). Az egyediesítő művelet nincs hatással az adatok sorrendjére. Tízféle előfordulást kapunk. Generikus lista:

8. Betűméret lépésköze

A szófelhőben a szavak gyakorisága alapján határozzuk meg a betűméretet. A betűméret 32-ről indul és fokozatosan csökken 16-ig. A betűméret léptetéséhez a tízféle gyakoriság/előfordulás meghatározza a stepFontSize  lépésközt. Lépésköz:

9. Betűméret kiszámítása

Csoportváltást alkalmazunk és a csoportot gi-vel indexeljük. Egy csoportba azok a POJO objektumok tartoznak, amelyeknél a szavak előfordulása megegyezik. Az algoritmus 2. lépésében az aktuális csoportra érvényesen kiszámítjuk a betűméretet ( fontSize), ami az algoritmus 3. lépésében a csoportba tartozó minden POJO objektumnál beállításra kerül a setFontSize() POJO eljárással. Az algoritmus 4. lépésében léptetjük a csoport gi indexét. A POJO-k esetén először csak a word és count tulajdonságok kerültek beállításra, de most már a fontSize tulajdonság is értéket kapott. Generikus lista:

10. HTML tartalom előállítása

A generikus lista POJO objektumain végighaladva, a forEach() záró művelettel összeállítható a weboldal szófelhőt tartalmazó része ( sbHTML). A 71 db szóból álló szófelhő HTML forráskódjának mérete 3409 bájt. HTML forráskód:

Eredmény

Szöveges formában:

lorem ipsum szófelhő

Képként (a 3. lépés részeredményéből a WordClouds.com weboldalon generálva):

lorem ipsum szófelhő eredmény

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának több alkalmához is kötődik. A Stream API-val és a lambda kifejezésekkel sokszor foglalkozunk.

Egy matematika érettségi feladat megoldása programozással 2018

érettségi logó

érettségi logóA 2018-as középszintű matematika érettségi feladatsor 10. feladata inspirált arra, hogy a programozás eszköztárával oldjuk meg ezt a feladatot. Szükséges hozzá néhány programozási tétel: sorozatszámítás, eldöntés, kiválasztás. Érdekes belegondolni, hogy mennyire más lehetne a problémamegoldás, ha programozhatnánk a matematika érettségi vizsgán. A teljes feladatsor a megoldásokkal együtt letölthető az oktatas.hu-ról.

10. feladat

Adja meg az alábbi adathalmaz móduszát, mediánját és terjedelmét!
2; 6; 6; 6; 6; 6; 3; 3; 4; 4; 4; 5; 5; 5; 5

Tervezés

A Java 8 által biztosított újdonságok közül használunk néhányat. Célszerű konstans tömbben tárolni a megadott számsorozatot, ami könnyen konvertálható generikus listába. Alkalmazkodni kell ahhoz, hogy a lista indexelése 0-tól lista.size()-1 -ig értelmezhető. Hasznos, ha a konkrét feladatok helyett általános megoldásokban gondolkodunk és a feladatot 3 metódusra bontjuk, amelyek ellenőrzéseket is végeznek. Például extrém esetek:

  • ha a lista üres, akkor nincs módusz, medián, terjedelem,
  • ha a lista egyetlen elemből áll, akkor a módusz és a medián megegyezik az elemmel, a terjedelem pedig nulla,
  • ha leggyakrabban több különböző szám is előfordul, akkor a módusz ezek közül a (leg)kisebb számot adja vissza.

Elvárjuk, hogy probléma esetén a metódusok dobjanak kivételt. Lényeges, hogy a referencia szerinti paraméterátadás során megváltozna a listában az elemek sorrendje, mert a megoldás igényli az elemek rendezettségét, akkor készüljön másolat az adatszerkezetről, hogy egy-egy részfeladat megoldása nem járjon azzal a mellékhatással, hogy az eredeti adatszerkezetben megváltozik az elemek sorrendje. Felhasználjuk a primitív típusú változók és a csomagolóosztályok közötti konverziós lehetőségeket: autoboxing és unboxing.

Megoldás: módusz

A módusz a lista leggyakoribb értékét adja meg. Másképpen az az érték, amelyik az adatsorban a legtöbbször előfordul.

A modusz() metódus átveszi a szamLista-t és készít róla lista néven egy másolatot, majd utóbbit növekvő sorrendbe rendezi. A másolat a Stream API-val készül el. Ezután csoportváltás algoritmussal feldolgozza a listát. Egy csoportba az azonos számok kerülnek és léptetés közben a belső ciklus megszámolja, hogy hány azonos szám alkotja az aktuális csoportot. Végül összehasonlítás következik a szélsőérték-kiválasztás ( aktSzamDb>maxAktSzamDb) beépítésével.

Megoldás: medián

A medián a lista középső értéke, amelynél az ennél kisebb és nagyobb elemek száma azonos. Rendezett adatsornál páratlan elemszám esetén a középső elem, illetve páros elemszám esetén a két középső elem átlaga.

A median() metódus átveszi a szamLista-t és készít róla lista néven egy másolatot, majd utóbbit növekvő sorrendbe rendezi. Ezután páros elemszám esetén visszaadja a két középső elem átlagát, illetve páratlan elemszám esetén a középső elemet. A metódusnak valós értéket ( double) kell visszaadnia, mert a két középső elem átlaga nem feltétlenül egész szám.

Megoldás: terjedelem

A terjedelem azt mutatja meg, hogy mekkora értékközben ingadoznak a lista elemei. A terjedelem az adatok változékonyságának „legdurvább” jellemzője, ami a szélsőértékek (minimum és maximum) közötti különbséget jelenti.

A terjedelem()  metódus átveszi a szamLista-t paraméterként és visszaadja a két szélsőérték különbségét, amelyek a Collections  osztály metódusaival könnyen előállítható. Persze egyetlen ciklussal is megkaphatnánk a két szélsőértéket.

Eredmény

A vezérlést az alábbi main()  metódus végzi el:

A konzolon az alábbi eredményt kapjuk:

Ajánljuk matematika érettségi feladat címkénket, mert a témában évről-évre blogolunk.

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának 17-28. óra: Objektumorientált programozás alkalmaihoz kötődik.

Péntek 13

Péntek 13

Péntek 13Sokan szerencsés vagy balszerencsés napnak tartják a péntek tizenharmadikát. Évente 1-2-3 alkalommal megtörténik, hogy a hónap 13. napja péntekre esik (minden vasárnap kezdődő hónapban). A hónap 13. napja valamivel valószínűbben péntekre esik, mint a hét bármely más napja. Átlagosan 212,35 naponként fordul elő péntek 13. Előfordulhat két egymást követő hónapban is, de akár 14 hónap is eltelhet két péntek 13 között.

A nap említése sok helyen előfordult: regényekben, filmekben, híres emberek születése vagy halála is esett péntek 13-ra. Átlag alatti közlekedési baleset szokott előfordulni ezeken a napokon – talán mert az emberek óvatosabbak. Kimutatható összefüggést/korrelációt, „péntek 13 hatást” figyeltek meg a tőzsdén is.

Hasznos lehet, ha írunk egy Java programot, amely néhány egymást követő év esetén listázza a konzolra azokat a hónapokat, amikor 13-a péntekre esik.

Tervezés

Legyen egy listFriday13(year) eljárás, amely a paraméterként átvett évben kiírja azokat a hónapokat a konzolra, amelyekben 13-a péntekre esett/esik. Például: 2017: január, október. A hónapok nevei magyar nyelven jelenjenek meg. Az adott év hónapjain végighaladó ciklus legyen hatékony. Optimalizáljunk a ciklus lépésszámára! A ciklus álljon le, ha már talált 3 hónapot (mivel nem lehet több).

1. megoldás

A megoldást a tematika Tömbök témakörében az alábbiak szerint készíthetjük el. Előismeretek: változók, operátorok, ciklusok, programozási tételek, metódusok, tömbök, String összehasonlítás. Az ismert öröknaptár algoritmusokból implementáljuk az egyiket, például:

A listFriday13v1(year) eljárásban az elemi döntés egyszerű: dayOfWeek(year, month, 13).equals("Friday"). Épít az öröknaptárt megvalósító – saját – szöveget visszaadó függvényre. A függvény az algoritmus szerinti kódok előállításához ( centuryCode, monthCode, dayCode) felhasználja a szökőév ( isLeapYear(year)) függvényt, valamint két – konstansnak is tekinthető – névtelen tömböt ( new int[], new String[]).

2. megoldás

A megoldást a tematika Objektumorientált programozás témakörében az alábbiak szerint készíthetjük el. Felhasználjuk eddigi ismereteinket és a JDK beépített dátumkezelő (tároló, formázó) funkcióit (osztályok, interfészek, konstansok, felsorolások).

A listFriday13v2(year) eljárás a Calendar absztrakt osztály konstansait használja fel az elemi döntéshez: date.get(Calendar.DAY_OF_WEEK)==Calendar.FRIDAY. A dátumot a GregorianCalendar konstruktora példányosítja és figyelni kell a 0-bázisú hónapkezelésre. A dátum formázása során ( dfMonth) beállítjuk a megfelelően paraméterezett ( "hu") Locale típusú objektumot és a hónap hosszú nevét kérjük ( "MMMM"). A metódus generikus listába gyűjti a kiválasztott hónapok nevét, amiket végül a String.join() függvény fűz össze a megjelenítéshez.

Eredmény

A vezérlésben egy ciklus 2017-től 2036-ig szervezve az alábbi eredményt adja:

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam tematikájához kötődik a fentiek szerint: 13-16. óra: Tömbök alkalom, illetve 17-28. óra: Objektumorientált programozás alkalom.

Aki gyakorolna a témához kötődően: írjon olyan Java programot, ami listázza a konzolra a 21. század éveit olyan hónapokba csoportosítva, amikor 13-a péntekre esik. Egy év többször is előfordulhat. Például: január – 2006, 2012, 2017, 2023, 2034, 2040, 2045, 2051, 2062, 2068, 2073, 2079, 2090, 2096. A megoldásokat hallgatóinktól az ILIAS-ra, érdeklődőinktől hozzászólásban várjuk.

Kígyókocka grafikus felületen

Kígyókocka

KígyókockaA JavaFX grafikus felhasználói felületének és eseménykezelésének megvalósítása némileg eltér más Java GUI implementációk működésétől, például swing vagy Java3D. Főként animációk során hasznos használni. Megközelítése természetesen objektumorientált: a térbeli objektumok koordinátái, viselkedésük, transzformációkkal valósul meg, és azok is objektumok. A korábban elkészített konzolos kígyókocka programot valósítjuk meg most JavaFX GUI-val.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

A program működése

Kígyókocka JavaFX grafikus felületen

A program megvalósítása

A start() JavaFX életciklust indító eljárás felépíti a createGridUI() függvényt meghívva a felhasználói felületet (színpad/jelenet JavaFX-ben), beállítva a méreteket, címsort, és meghívja az eseménykezelésért felelős handleRotateButtons() eljárást.

A createGridUI() függvény a grafikus felhasználói felület elemeit paraméterezi (szerepe szerint Factory metódus). Öt elemből álló rács ( GridPane osztályú grid nevű objektum) készül el, amelyre nyilakat tartalmazó nyomógombok (pl.: Button típusú btLeft objektum) kerülnek fel a négy égtájnak megfelelően, valamint rajta középen helyezkedik el a kígyókocka 3D megjelenítését megvalósító objektum. A nyilak entitásai az Unikód karaktertáblából származnak. A kígyókocka objektumot a meghívott createSnakeCube() függvény hozza létre. A Node osztályú snakeCube nevű objektum geometriai transzformációs objektumot is hozzá kell rendelni: ez most a négyirányú forgatást megvalósítani képes névtelen Rotate osztályú objektum lesz. A forgatást 5 paraméterrel célszerű beállítani (van rá megfelelő túlterhelt konstruktor), ezek rendre: szög, X, Y, Z tengely origója és a forgatás tengelye. Az objektumok tulajdonosi hierarchiája swing-es szemmel nézve szokatlannak tűnik, de szemléletben legalább azonos a Java3D és a JavaFX megvalósítás.

A createSnakeCube() függvény előállítja a színpadra/jelenetbe kikerülő kígyókockát Node osztályú objektumként. A konstans CUBE tömb egységvektor rendszerben tartalmazza a kígyót alkotó kockák középpontjait. Az első ciklus mindezt nagyítást alkalmazva skálázza. A második ciklus koordináta és pont transzformációk alkalmazásával ( moveToMidPoint: eltolás középre, rotateAroundCenter: forgatás a középpont körül) a kiinduló állapotnak megfelelő méretben és pozícióban elhelyezi a kígyó útvonalát mutató hengerobjektumokat. A konstrukciós és transzformációs műveletek esetén alkalmazkodni kell ahhoz, hogy a JavaFX koordinátarendszerben az X jobbra, az Y lefelé, a Z pedig befelé (a nézőponttól távolodva a térben) növekszik. A matematikai hátteret részletesen most nem magyarázzuk el.

A handleRotateButtons() eljárás a forgatás 4 nyíl eseménykezelésének hozzárendelést végzi el. A nyomógomb objektumok setOnAction() hozzárendelő metódusának paramétere EventHandler funkcionális interfésszel és lambda művelettel működik. A forgatás irányát hozzárendeljük a megfelelő nyomógombhoz. Ez még csak végrendelkezés a jövőre: csak definiáljuk, hogy minek kell majd történnie, ha bekövetkezik az esemény (valamelyik nyílra/nyomógombra kattint a felhasználó).

A rotateSnake() eljárás minden nyíl feliratú nyomógombra kattintva reagál a bekövetkezett eseményre. A rotateAxis objektum a forgatás tengelye, a paraméterként átvett direction enum-tól függ, szinkronban azzal a nyomógombbal, amelyikre kattintott a felhasználó.

Ötletek a továbbfejlesztésre

  • Lehetne többféle irány is, például a négy sarokba átlós vagy mélységi irányú elforgatással.
  • Beépülhetne többféle transzformáció is, például skálázás (kicsinyítés, nagyítás), eltolás (közelítés, távolítás).
  • A kígyó útvonalát mutató hengerobjektumok kirajzolásának sorrendjén lehetne változtatni, mert a megjelenítés nem tökéletes. Jelenleg néhány helyzetben lehetetlennek, Escher lehetetlen konstrukcióihoz hasonlónak tűnhet a kígyókocka. Ha a tér mélységéből a nézőpont felé közeledve rajzolnánk ki a hengerobjektumokat, akkor a 3D látvány nem sérülne.

Tanfolyamainkon JavaFX grafikus felülettel hangsúlyosan nem foglalkozunk, de egy-egy kész forráskódot közösen megbeszélünk, és össze is hasonlítjuk a swing-es változattal. Fejlesztünk játékprogramot, de inkább konzolosan, vagy swing-es ablakban, vagy webes alkalmazásként.

A grafikus felületek felépítésének megismerése fontos lépcső az objektumorientált programozás elmélyítéséhez, gyakorlásához. A grafikus felületekhez egy másik lényeges szemléletváltás is kapcsolódik, hiszen a korábbi algoritmusvezérelt megközelítést felváltja az eseményvezérelt (eseménykezelés).

Tudatosan hangsúlyozott MVC-s projektben megoldva a feladatot, a modell rétegben tárolhatnánk többféle kígyókocka megjelenítéséhez és animációjához szükséges adatszerkezetet és transzformációs objektumokat/metódusokat is és a nézet/vezérlő rétegekben biztosíthatnánk ezek közül a kijelölést/kiválasztást menüvel, ikonokkal, eszköztárral, gyorsbillentyűkkel.

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

Tanfolyamaink orientáló moduljának 9-12. óra: Mesterséges intelligencia alkalmához kapcsolódóan a kígyókocka véletlenszerű előállítása helyett stratégiával rendelkező visszalépéses algoritmust specifikálhatunk és implementálhatunk.

Ez egy két részből álló blog bejegyzés 2. része. A blog bejegyzés 1. része itt található.

Java fejtörők – haladó

Java fejtörők

Java fejtörőkJava fejtörők – csapdák, buktató, és szélsőséges esetek. Ez egy könyv címe, amelynek szerzői J. Bloch és N. Gafter. Magyar nyelven a Kiskapu Kft. jelentette meg. A 2010-es magyar kiadás a 2005-ös angol nyelvű kiadás fordítása. A könyv weboldaláról (http://www.javapuzzlers.com), letölthető a 95 fejtörőhöz tartozó mintapéldák gyűjteménye, és elérhető a 270 oldalból minta fejezetként 28 oldalnyi tartalom 9 fejtörővel és azok részletes magyarázataival.

A két részre bontott blog bejegyzés a könyv anyagából válogatva készült el. Az első rész a bevezetés. Ez a második rész, haladó szintű példákkal. Néhány példát továbbfejlesztettem.

7. fejtörő: Mit ír ki program a konzolra?

Kivételkezelés nélkül arra számítanánk, hogy a Hello World! nem jelenik meg a konzolon, mert a wordHard() metódus feltétel nélkül rekurzív módon folyamatosan újrahívja önmagát és az emiatt keletkező StackOverflowError hibával elszáll a program. A kivételkezelés természetesen módosítja a program működését.

Ha azt feltételezzük, hogy minden Throwable utódosztályból futás közben létrehozott objektum kivételkezeléssel elkapható, akkor végtelen ciklusnak tűnik a vezérlés, hiszen a try blokkban hibát okozó metódushívásra a finally blokkban újra ugyanannak a metódusnak a meghívásával reagálunk, amelyik korábban a hibát kiváltotta. Ez a gondolatmenet tévút. A kulcsszó a rekurzív vezérlést megvalósító verem adatszerkezet mérete. Részletes indoklás a blog bejegyzés végén található.

8. fejtörő: Mit ír ki program a konzolra?

Természetesen NullPointerException-re gyanakszunk, pedig a program hibátlanul működik. A kulcsszó a statikus metódusok minősítése, vagyis annak jelölése, hogy melyik osztálytól vagy objektumtól kérjük annak végrehajtását. Részletes indoklás a blog bejegyzés végén található.

9. fejtörő: Mit ír ki a program a konzolra?

Arra számítunk, hogy a kiírás 1999-12, de ehelyett 2000 1-et látunk a konzolon. Tudjuk, hogy a Date osztály jó része már deprecated és ezen próbáltak javítani a Calendar osztállyal. Bár ne tették volna. A kulcsszó az ős dátumkezelést megvalósító API rejtelmeiben van. Részletes indoklás a blog bejegyzés végén található.

10. fejtörő: Mit ír ki a program a konzolra?

Nem tűnik egyértelműen eldönthetőnek a helyzet, ezért szintaktikai hibára gyanakodhatunk. Azonban a forráskód helyes, a program futás közben sem dob kivételt/hibát és a konzolon megjelenik a White. Szokatlan, hogy nagybetűvel konstansokat szokás jelölni, pedig nincs erre utaló final a forráskódban. A kulcsszó a sorrendiségen van, ha ugyanabban a hatókörben/blokkban van azonos nevű változó és típus/osztály. Részletes indoklás a blog bejegyzés végén található.

11. fejtörő: Mit ír ki a program a konzolra?

A program helyes és egyértelműnek tűnik. A konzolra az s1 szövegtömb elemei kerülnek ki véletlenszerűen összekeverve. Finomítsunk a kérdésen. Vajon minden lehetséges permutáció azonos eséllyel fordul elő? Ha ez a kérdés egyáltalán felmerül, akkor a válasz nyilván nem. A kulcsszó most egy kis matematika. Részletes indoklás a blog bejegyzés végén található.

12. fejtörő: Mit ír ki a program a konzolra?

Ez a forráskód nem úgy működik, ahogyan a könyv írja. Meglepő módon nem a [3, 1, 4, 1, 5, 9]-et adja, hanem az [1, 1, 3, 4, 5, 9]-et. Némi indoklás a blog bejegyzés végén található.

Részletes indoklások

  • 7. fejtörő: ha a try blokkban folyamatosan meghívja saját magát a workHard() metódus, akkor előbb-utóbb betelik a verem. Ekkor a finally blokkra kerül a vezérlés, ahonnan újra hívja saját magát a workHard() metódus. Persze követni kell, hogy a rekurzió végrehajtása során a lefelé haladó vagy a felszálló ágon vagyunk és nem mindegy, hogy melyik szinten. A háttérben egy teljes bináris fa bontakozik ki, amelynek mélysége azonos a verem méretével, mélységi korlátjával. Ezt a teljes bináris fát járja be a program, azaz mélységi fabejárás. Egy n mélységű teljes bináris fa elemeinek száma 2n-1. A verem mérete a virtuális gép beállításaitól függ, több ezer mélységű is lehet. Végtelen ciklusról tehát nincs szó. Ugye milyen izgalmas? További részletek a könyv 100-102. oldalán találhatók.
  • 8. fejtörő: a végrehajtás kiértékeli a statikus greet() metódus hívásának minősítő kifejezését, de figyelmen kívül hagyja a kapott értéket. A metódust végrehajthatnánk Null.greet()-ként vagy közvetlenül (minősítés nélkül) meghívva is. További részletek a könyv 122-124. oldalán találhatók.
  • 9. fejtörő: a Date osztály a hónapokat nulla bázissal kezeli, ezért csak 0-11-ig „van értelme”. Számíthatnánk a tömb vagy szöveg túlindexelésénél tapasztaltakhoz hasonlóan kivételre, de nem ez történik. A 12. hónap a következő év első/nulladik hónapját jelöli. Ezért látjuk a konzolon a 2000-et, amit egy kötőjel követ. A Date.getDay() deprecated metódus pedig a dátumobjektumban tárolt nap adott héten (nem hónapban!) elfoglalt helyét adja meg, ami nullával, azaz vasárnappal indul. Tehát a konzolon megjelenő 1 nem a 2000. januárt jelenti, hanem azt, hogy a 2000. január 31. hétfőre esik. Aki ezek után meri használni a régi dátumkezelő API-t, magára vessen. További részletek a könyv 141-143. oldalán találhatók.
  • 10. fejtörő: ha ugyanabban a hatókörben/blokkban van azonos nevű változó és típus/osztály, akkor a változó neve az elsődleges. Ha betartjuk a névadási konvenciókat ( ClassName, objectName, CONSTANT_NAME), akkor nem adódhatnak ilyen gondok. Még egy csavar van: ha az előző elnevezési módosításokat megtesszük, akkor a program a Black-et írja ki a konzolra. További részletek a könyv 161-163. oldalán találhatók.
  • 11. fejtörő: konkrét esetből általánosítunk. 4 elemre a ciklus 4-szer hajtódik végre és minden lépésben kiválaszt egyet a 0 és az 3 indexű elemek közül, ami 44=256-féle lehetséges eredményt ad. Ha az r objektum jól működik, akkor az egyes futások esélye/valószínűsége megegyezik. 4 elemű tömb elemeinek 4!=24 (faktoriális) féle permutációja (lehetséges sorrendje) van. Mivel a 256 nem osztható 24-gyel, így biztos, hogy a shuffle() metódus bizonyos permutációkat gyakrabban állít elő, mint másokat. Általánosan: nn nem osztható n!-sal, ha n>2 egész szám. Vajon mi történik, ha egy 52 lapos pakli kártyát keverünk össze? Vajon milyen érdekességet vet ez fel? Minden poént nem lövünk le itt a blogban. További részletek a könyv 228-232. oldalán találhatók.
  • 12. fejtörő: ez a tankönyv utolsó példája. A felvetett gondolat nagyon frappáns: az összehasonlító rész „ha fej, én nyerek, ha írás, te veszítesz” tüneteitől szenved. További részletek a könyv 232-233. oldalán találhatók.

 

Állásinterjúkon időnként visszaköszönnek hasonló fejtörők, de ezekkel óvatosan kell bánni. Egy programozási nyelv „joghézagainak”, buktatóinak, szélsőséges eseteinek ismerete a könyv szintjét elérő ismeretanyaggal nem lehet elvárt még egy meghirdetett senior pozíció esetén sem. Ezen fejtörők ismerete (vagy nem tudása) egy jelöltről nem árulja el a mindennapokban használható szakmai tudás meglétét/hiányát. De nyilván aki szakmailag folyamatosan fejlődik és mindenféle keretrendszert alkotó forráskódokban turkál, elemez, előbb-utóbb találkozik ezekkel/ilyenekkel.

Tanfolyamainkon nem kifejezetten foglalkozunk hasonló problémákkal, de azért időnként feszegetjük a határokat. Természetesen részletesen indokoljuk, ha előkerül valamilyen hasonló eset. Általánosságban nem célunk, hogy extrém eseteken keresztül, a programozási nyelv gyenge pontjaira kihegyezve oktassuk a Java programozási nyelvet.