Fibonacci-spirál

Fibonacci nap

Fibonacci nap 2018A Fibonacci-spirál a népszerű Fibonacci-sorozat elemei által meghatározott oldalhosszúságú négyzetekbe rajzolt maximális sugarú negyedkörök megfelelően összeillesztett darabjaiból/sorozatából áll. Sokszor hasonlítják az arany spirálhoz (jól közelíti), amely az aranymetszéshez kötődik.

A Fibonacci-spirál

Vegyük a Fibonacci-sorozat első 10 elemét! Rajzoljuk egymás mellé az alábbi elrendezésben belülről kifelé haladva az 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 oldalhosszúságú négyzeteket (az alábbi ábrán vékony sárgával jelölve). Piros színnel rajzoljuk bele a négyzetekbe a négyzet oldalhosszával megegyező sugarú negyedköröket. A negyedkörök megfelelő elrendezésben folytonos görbét alkotnak, és ezt nevezzük Fibonacci-spirálnak (az alábbi ábrán vastag pirossal jelölve).

Fibonacci-spirál 1

A rajzolás bármeddig folytatható, mert a sorozat végtelen, a négyzetek illeszkednek és az ábra rekurzív, önhasonló. Az alábbi animáció mutatja, hogyan alakul a spirál a nézőpont közelítésével. A viselkedés távolítás során is azonos lenne.

Fibonacci-spirál 2

Korábban blogoltunk már a Fibonacci napról, amelyet minden évben november 23-án ünneplünk. A sorozat első néhány eleméből összeáll a 11.23. és értelmezhető dátumként. Most nem a sorozat elemeinek előállítására fókuszálunk, hanem arra, hogy ezekből felépítsük a Fibonacci-spirált.

Készítsünk Java programot!

Grafikus felületű Java programot készítünk, amely 21 animációs fázisban mutatja be a Fibonacci-sorozat első 10 eleméből álló Fibonacci-spirál felépítését. A rajzolás fázisai:

  • Az 1. fázis a kiindulópontként tekinthető fehér, üres rajzlap. A rajzlap fekvő, mérete 890*550 pixel, amelyre éppen elfér a 10 negyedkörből álló spirál.
  • A 2-11. fázisban megfelelő pozícióba/koordinátákra kerülnek fel az ábra vázát alkotó négyzetek, belülről kifelé haladva. A négyzetek oldalainak hosszúsága a sorozat elemeinek megfelelő. A szomszédos négyzetek különböző színekkel kitöltöttek és mindegyikben megjelenik a sorozat megfelelő eleme.
  • A 12-21. fázisban – szintén belülről kifelé haladva – a négyzetek törlődnek és helyükre a spirált alkotó negyedkörök kerülnek fekete színnel. A 21. fázist tekintjük végeredménynek.

A fázisok kézzel, nyilakkal jelölt (Első, Előző, Következő, Utolsó) vezérlő nyomógombokkal megjeleníthetők, illetve egyben, időzítve animációként is lejátszható a rajzolási folyamat. Az elkészült program működése megfigyelhető az ábrán:

Fibonacci-spirál Java program

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 Java SE szoftverfejlesztő tanfolyamunkon, a szakmai modul Objektumorientált programozás témakörét követő 29-36. óra Grafikus felhasználói felület alkalmain már tudunk egyszerűbb szimulációs programot tervezni, kódolni, tesztelni.

Koch-görbe rajzolása

Koch-görbe

Koch-görbeA Koch-görbe egyike a legrégebben ismert egyszerű fraktáloknak. Mint ilyen, önhasonlóan rekurzív. Az önhasonlóság azt jelenti, hogy az ábra tetszőleges részét felnagyítva mindig hasonló/ugyanolyan részek jelennek meg (a méretaránytól függetlenül). Az n=1 szinten a Koch-görbe kiindulópontja egy szabályos háromszög. A n+1-edik szinten az n-edik szinten található szakaszokat harmadoljuk, és a középső szakasz helyére egy harmad akkora háromszög két szárát illesztjük (az alapját kihagyjuk). Ezt rekurzívan folytatva kapjuk meg a Koch-görbét, másképpen Koch-féle hópelyhet.

Írtam egy egyszerű Java programot, amely n=1-től 9-ig paraméterezhetően kirajzolja a Koch-görbét egy grafikus felületre. Így működik:

Koch-görbe rajzolását bemutató program működése

A program elkészítéséhez néhány alapvető dolgot kell csupán tudni:

  • Vászontechnikával tudunk swing GUI felületre ( Graphics osztályú g objektum) rajzolni, ahol a koordináta-rendszer origója egy téglalap alakú terület bal felső csúcsa, X jobbra növekszik, Y pedig lefelé növekszik.
  • Kétféle szín áll rendelkezésre: háttérszín (most Color.WHITE), illetve rajzolószín (most Color.BLUE).
  • A rajzoláshoz grafikai primitíveket használhatunk, például pont, szakasz, téglalap, ellipszis. Szakaszt két végpontjának koordinátáival tudunk rajzolni a drawLine() metódussal.
  • Be kell állítani a vászon méreteit, azaz annak a komponensnek ( JPanel-ből öröklött KochPanel osztályú pnKoch objektum) a méreteit, amelyre ráfeszül a vászon.
  • Egy Slider osztályú sSzint nevű vezérlőobjektum ChangeListener figyelőinterfész stateChanged() eseménykezelő metódust implementáló objektumával paraméterezzük a rajzolást 1-től 9-ig.
  • A pnKoch objektumnak küldött repaint() üzenet/metódushívás meghívja a felüldefiniált paintComponent() metódust.

A szakasz négy darab harmad akkora szakaszra osztását a megfelelően paraméterezett rekurzív metódushívások oldják meg az alábbi lépéseket követve:

Koch-görbe rajzolásának fázisai

A rekurzív rajzolást a koch() metódus végzi el, ahol a fraktál szabályának megfelelően szakaszharmadolás és a szükséges pontok koordinátáinak (szakaszok végpontjai) kiszámítása történik:

A Koch-görbének van néhány érdekes tulajdonsága:

  • kerülete minden rekurzív lépésben minden határon túl növekszik, azaz a végtelenhez tart,
  • területe véges, hiszen minden rekurzív lépésben belefér a háromszög köré írható körbe,
  • dimenziója tört, ~ 1,261859.

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 9-12. óra: Metódusok, rekurzió alkalomra építő 29-36. Grafikus felhasználói felület alkalomhoz kötődik.

Fibonacci nap

Fibonacci nap

Fibonacci nap 2018A Fun Holidays – Fun, Wacky & Trivial Holidays weboldal sokféle különleges ünnepnapot listáz. Ezek leírása többnyire vicces, emlékezős, de néhány igazán érdekes, régi-régi hagyományt elevenít fel.

Ma van (november 23.) a Fibonacci nap. Fibonacci középkori matematikus volt, ő tette közismertté a Fibonacci-sorozat-ot. A (0), 1, 1, 2, 3, 5, 8, 13, 21, 34, sorozat igen népszerű azok közében is, akik programozást tanulnak. A sorozat első két eleme 1 és 1 (ha szükséges, akkor nulladik elemmel is dolgozhatunk), és minden további elem az előző két elem összege. Többféle történet is fűződik ehhez, talán az egyik legismertebb a nyúlpárok szaporodásához kötődik.

Honnan származik a Fibonacci nap? A mai nap hh.nn. formátumban 11.23. , és a számjegyek részei a Fibonacci-sorozatnak. Mindössze ennyi, ilyen egyszerű. 😉

A sorozat elemei könnyen előállíthatók néhány változó használatával, ha a kezdő programozó már ismeri a ciklust, mint algoritmikus építőelem – ez az iteratív megoldás. A rekurzív megoldás tipikus rossz megoldásként ismert, lássuk ennek Java megvalósítását:

Ha kiadnám a fenti Java forráskódot papíron ezt egy dolgozatban, zárthelyin, állásinterjú szakmai részén azzal a kérdéssel, hogy mit ír ki a program a képernyőre, akkor bizony sokan bajban lennének. Meg is történt ez már sokszor, tapasztalatból írom. A rekurzió első leszálló ágáig szinte mindenki eljut, de az ott induló első felszálló ágat követően sokan belezavarodnak a részlépések egymásutániságába. A végeredményt szinte mindenki tudja, de itt most arra helyezzük a hangsúlyt, hogy hogyan jutunk el odáig. Persze n=5-re fib(5)=5. Alig fordult még elő, hogy valaki hibátlanul leírta volna az alábbi eredményt:

A megoldás során – emlékeztetek arra, hogy ez atipikus megközelítés – sok-sok redundáns lépés történik. Hiszen például a fib(3)-at tudni kell a fib(4)-hez és a fib(5)-höz is, hiszen fib(4)=fib(2)+fib(3) és fib(5)=fib(3)+fib(4), valamint ebben az implementációban nincs semmilyen emlékezet (puffer, adatszerkezet), amivel a sok feleslegesnek vélhető számítást elkerülhetnénk.

Nyert ügye lehet annak, aki „fejben összerakja” az alábbi fát – akár dinamikusan, menet közben hozzáadva és törölve elemeket – és ebben navigálva (ezt bejárva) válaszolja meg a kérdést:

Fibonacci-sorozat-n=5

Az alábbi animáció segíthet a megértésben: 45 lépésben mutatja be az aktuális részlépést/részfeladatot (leszálló ág) és/vagy az aktuális részeredményt (felszálló ág):

Fibonacci-sorozat-n=5

A Fibonacci-sorozat többféleképpen kapcsolódik a természethez, természeti jelenséghez, növényekhez, állatokhoz (virágszirmok száma, levelek elfordulása, napraforgók magjai, fenyőtoboz pikkelyei, nautilus háza, aranymetszés, zenei hangolás, zeneművek tagolása), felhasználható algoritmusok futási idejének becsléséhez, fa adatszerkezetek építéséhez is. Az aranymetszésről megoszlanak a vélemények: vannak akik szinte mindenben ezt látják (művészet: festészet, szobrászat), mások módszeresen cáfolják ezt (például Falus Róbert: Az aranymetszés legendája, Magyar Könyvklub, 2001, második, javított kiadás, ISBN 963-547-332-X).

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 9-12. óra: Metódusok, rekurzió alkalomhoz kötődik.

Ki kinek a vezetője?

Organogram logó

HR-organogram-logoAz SQL lekérdezések újabb típusát adják a hierarchikus lekérdezések. Az Oracle adatbázis-szerver már régóta támogatja ezt a lehetőséget. A hierarchia legtöbbször valamilyen fa adatszerkezethez kötődik. Ezek természetesen nem közvetlenül tárolódnak egy normalizált, relációs adatbázisban, de az adatok közötti kapcsolat értelmezése során felépíthető rekurzív módon a fa struktúra.

Hasonló feladat: Organogram készítése, reflexióra építve. Érdemes összehasonlítani a kétféle szemléletmódot.

Ki kinek a vezetője?

Az Oracle HR sémában az EMPLOYEES és DEPARTMENTS táblák között kétirányú 1:N kapcsolat van. Egy EMPLOYEE_ID egyedi kulccsal azonosított alkalmazotthoz tartozik egy nem kötelező DEPARTMENT_ID külső kulcs az EMPLOYEES táblában. Egy kivétellel minden alkalmazott részleghez hozzárendelt.

Oracle HR séma

Egy DEPARTMENT_ID egyedi kulccsal azonosított részleghez tartozik egy nem kötelező MANAGER_ID külső kulcs a DEPARTMENTS táblában. Minden olyan részlegnek van vezetője, amelyikhez legalább egy alkalmazott hozzárendelt. A DEPARTMENTS táblában csak olyan MANAGER_ID szerepelhet, amelyik megtalálható az EMPLOYEES táblában EMPLOYEE_ID-ként. A „legfelsőbb” szinten lévő vezetőnek nincs vezetője.

Előfordulhat, hogy egy-egy részlegen belül többszintű hierarchiát találunk az organogramban, ha a részlegek helyett az alkalmazottak oldaláról közelítjük meg a problémát. Ekkor építhetünk arra, hogy az EMPLOYEES tábla saját magával is kapcsolatban áll (reflexió): egy MANAGER_ID-hez több EMPLOYEE_ID is tartozhat. Másképpen: egy adott vezetőnek több beosztottja is lehet.

A hierarchikus (rekurzív) lekérdező parancs

Ki kinek a vezetője? - Hierarchikus SQL lekérdező parancs

A lekérdező utasítást bele kell építeni egy Java kliensprogramba (MVC architekturális tervezési minta szerint a modell rétegbe), ami JDBC alapon kapcsolódik az Oracle adatbázis-szerver HR sémájához olyan felhasználó nevében, aki csatlakozhat és lekérdezhet. Meg kell tervezni és felügyelni kell a biztonságos kapcsolatot (kivételkezeléssel), annak életciklusát (nyit, lekérdez, zár), valamint gondoskodni kell az eredménytábla megjelenítéséről.

Az eredménytábla (részlet)

Ki kinek a vezetője? - Eredménytábla

A keletkező eredménytábla exportálható Excel-be (XLS, XLSX formátumokba). Az első vagy utolsó oszlop adatait feldolgozva könnyen készíthető egy dinamikus adatmodellel rendelkező, fa struktúrát megjeleníteni képes komponens/felület, ahol szabadon böngészhető a szervezeti hierarchia.

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 adatbázis-kezelő tanfolyam 9-12. óra: Oracle HR séma elemzése, 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalomhoz kapcsolódik.

Az SQL forráskód formázásához a Free Online SQL Formatter-t használtam.

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.