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 (31).

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.

Fát építünk

Fát építünk

Fát építünkAz adatok strukturális és könnyen értelmezhető formában való megjelenítése egy szoftver felhasználói felületén átgondolt tervezést igényel. Az adatokhoz hozzá kell jutni, ki kell választani a megfelelő grafikus komponenst, a mögötte lévő adatmodellt, össze kell ezeket kötni. Gyakran előforduló feladat, hogy táblázatosan is ábrázolható adatokból – felhasználva az adatok közötti összefüggéseket és kapcsolatokat – csoportosítva jelenítsünk meg hierarchikusan, fa struktúrában, kinyitható-becsukható formában, ahogyan ezt a felhasználók jól ismerik a fájl- és menürendszereket használva.

Fát építünk kétféleképpen

Adatbázisból, az Oracle HR sémából lekérdezünk két összetartozó nevet: részleg és alkalmazott. A lekérdezés során figyelünk a megfelelő sorrendre, ami a későbbi feldolgozást megkönnyíti. Adatainkat részlegnév szerint növekvő, azon belül alkalmazott neve szerint is növekvő – ábécé szerinti – sorrendbe rendezzük. A vezérlő rétegben két függvényt írunk, amely a modell rétegtől jut hozzá az adatokat tartalmazó generikus listához – átvett paraméterként –, és a visszaadott érték a nézet réteghez kerül.

A csoportváltás algoritmust használjuk, amely 5 blokkból épül fel. A külső ciklus előtti 1. blokk és utáni 5. blokk egyszer hajtódik végre, az előkészítő és lezáró tevékenységek tartoznak ide. A külső ciklus elején és végén található 2. és 4. blokk a belső cikluson kívül fut le, csoportonként, kategóriánként, részlegenként egyszer (most összesen 11-szer mindkettő). A 3. blokk a belső cikluson belül található, és alkalmazottanként egyszer hajtódik végre (most összesen 106-szor).

Háromszintű fát építünk: a gyökérbe (0. szint) fix, beégetett szövegként kerül a cég neve és a teljes létszám. Az 1. szinten jelennek meg a részlegek nevei és a hozzájuk tartozó létszámok. A 2. szint az alkalmazottak neveiből áll.

1. megoldás

A megoldás faKeszit1() függvénye szöveges adatot eredményez. Ez jól használható teszteléshez: megvan-e az összes adat, megfelelő-e a részlegek sorrendje azon belül az alkalmazottak sorrendje, működik-e a csoportosítás, rendben van-e a megszámolás?

A faKeszit1() függvény egy sok lépésben összefűzött (konkatenált) szöveget ad vissza. Az 1. blokkban előkészítjük a fa gyökerét, ami StringBuilder típusú, hiszen sokszor manipuláljuk és inicializáljuk a lista indexelésére használt i ciklusváltozót. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük az ehhez tartozó alkalmazottak nevét tároló generikus listát ( faReszlegAlkalmazott). Az aktReszleg-hez tartozó alkalmazottak neveit összegyűjtjük a 3. blokkban. Egy részleg feldolgozását a 4. blokkban fejezzük be a fa aktuális 1. és 2. szinten lévő elemeinek szövegbe való beszúrásával. A belső ciklushoz kötődően megszámolást nem kell alkalmaznunk, hiszen az adott részlegben dolgozó alkalmazottak száma a generikus listától elkérhető ( size()). Építünk arra, hogy a külső ciklusból nézve az egymás után végrehajtódó 2. és 4. blokkban az aktReszleg nem változik meg. A 2. blokkban még nem tudjuk a fa aktuális 1. szintjét hozzáfűzni a szöveghez, hiszen a létszám csak a belső ciklusban felépülő kollekciótól kérhető el utólag. Szükséges némi késleltetés, hiszen a szöveg összefűzése és lényegesen egyszerűbb (mint utólag manipulálni megfelelő helyeken). Az 5. blokkban a csoportváltás algoritmushoz kötődő tevékenységünk nincs.

Az 1. megoldás eredménye

2. megoldás

A faKeszit2() függvénynél alkalmazkodunk ahhoz, hogy a JTree vizuális komponenshez DefaultTreeModel observable típusú modell szükséges, így ezzel térünk vissza ( faModell). A fa csomópontjai DefaultMutableTreeNode osztályú objektumok lesznek, amelyeknek a userObject tulajdonsága szükség esetén manipulálható. Az 1 blokkban beszúrjuk a fa gyökerét ( faGyoker), amihez a későbbiekben csatlakozik a fa többi eleme. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük – megjelenítendő szöveg nélkül – a faReszleg csomópontot. A 3. blokkban fabeli csomópontként a fa 1. szintjén megjelenő részleghez névtelenül hozzáadjuk a fa 2. szintjére kerülő – aktuális részleghez tartozó – alkalmazottak nevét. A 4. blokkban utólag módosítjuk a faReszleg csomópont megjelenítendő szövegét. Az aktuális részleg létszámát itt sem kell külön megszámolni, mert a faReszleg-től elkérhető ( getChildCount()). Az 5. blokkban itt sincs különösebb teendőnk.

A 2. megoldás eredménye

Fát építünk, képernyőkép

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

Attól függően, hogyan jutunk hozzá a megjelenítéshez szükséges adatokhoz, több tanfolyamunkhoz is kapcsolódik a feladat és a modell rétegben mindig másképpen tervezünk és implementálunk:

  • A Java SE szoftverfejlesztő tanfolyam 45-48. óra: Adatbázis-kezelés JDBC alapon, 1. rész alkalmán hagyományos SQL lekérdező utasítást készítünk JDBC környezetben.
  • A Java EE szoftverfejlesztő tanfolyam 25-32. óra: Adatbázis-kezelés JPA alapon alkalommal a perzisztencia szolgáltatásait vetjük be.
  • A Java adatbázis-kezelő tanfolyam 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 alkalmain hierarchikus lekérdezéseket használunk.