Ebben a Java projektben dátumok csoportosítását oldjuk meg, többféleképpen is. Mikor van erre szükség? Jelentés, kimutatás, riport, lista készítése során.
Példaként tekintsünk egy blogot. A blogban rendszeresen jelenik meg új tartalom (bejegyzés, poszt). Azért, hogy a blog hosszabb távon, sok bejegyzéssel is könnyen kereshető, átlátható, böngészhető legyen/maradjon a felhasználók, látogatók számára, célszerű:
- taxonómia kialakítása. Ez kategóriákat és címkéket jelent. Ebből címkefelhő vagy szófelhő is készíthető, ahogyan erről blogoltunk már: Címkefelhő generálása.
- marketing analitika használata. Ezek általában toplisták valamilyen könnyen hozzáférhető adat alapján. Például: látogatottság, népszerűség, eltöltött idő, hozzászólások száma, megosztások száma, egérmutató mozgása alapján hőtérkép. Ezek általában toplisták, amelyek eleje listázódik csökkenő sorrendben.
- dátum szerint is csoportosítani a blog bejegyzéseit. Érdemes megjeleníteni a legújabbtól a régebbi felé haladó (retrospektív) listát, hierarchikus fa struktúrát, lenyíló panelt. Mindez kombinálható toplistával. A csoportosítás elvégezhető igény szerint tetszőlegesen, például évente, negyedévente, havonta.
Lássuk, hogyan lehet megvalósítani a dátumok csoportosítását Java programozás nyelven!
Milyen adatokra van szükség?
Egy megadott zárt dátumtartományban véletlenszerűen előállítunk néhány dátumot. Nem számít, hogy különböznek-e. A dátumokat tároló listát érdemes csökkenő sorrendben tárolni. Minden dátum múltbeli, így ez a sorrend a jelenhez legközelebbitől halad a legtávolabbi felé. Például a Java program ezekkel a dátumokkal dolgozik (lapozható):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
2024.03.25. 2024.03.22. 2024.02.26. 2024.01.30. 2024.01.25. 2024.01.23. 2024.01.16. 2024.01.13. 2024.01.02. 2023.11.29. 2023.11.25. 2023.11.24. 2023.11.24. 2023.11.07. 2023.10.02. 2023.09.14. 2023.08.31. 2023.08.04. 2023.07.13. 2023.06.15. 2023.06.14. 2023.06.08. 2023.04.22. 2023.04.01. 2023.03.26. 2023.02.11. 2023.02.08. 2023.01.26. 2023.01.26. 2022.12.14. 2022.12.14. 2022.12.10. 2022.11.09. 2022.10.14. 2022.10.04. 2022.09.24. 2022.09.23. 2022.09.20. 2022.09.09. 2022.09.01. 2022.08.31. 2022.08.20. 2022.07.29. 2022.07.03. 2022.06.30. 2022.06.07. 2022.05.16. 2022.05.15. 2022.04.18. 2022.04.14. |
Milyen eredményeket kaphatunk?
Az évenkénti csoportosítás így jelenik meg:
|
Csoportosítás évente 2024. év (9) 2023. év (20) 2022. év (21) |
A havonkénti csoportosítás így jelenik meg (lapozható):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
Csoportosítás havonta 2024. március hó (2) 2024. február hó (1) 2024. január hó (6) 2023. november hó (5) 2023. október hó (1) 2023. szeptember hó (1) 2023. augusztus hó (2) 2023. július hó (1) 2023. június hó (3) 2023. április hó (2) 2023. március hó (1) 2023. február hó (2) 2023. január hó (2) 2022. december hó (3) 2022. november hó (1) 2022. október hó (2) 2022. szeptember hó (5) 2022. augusztus hó (2) 2022. július hó (2) 2022. június hó (2) 2022. május hó (2) 2022. április hó (2) |
Természetesen blog esetén gyűjtőoldalra mutató hivatkozást kell tenni a megjelenő elemekre. Azok az évek és hónapok nem jelennek meg, ahol nincs dátum (blog bejegyzés).
Hogyan kapjuk meg az eredményeket?
Természetesen Java nyelven programozva készítünk megoldást, sőt többféle megoldást. Ezek szépen összevethetők és mindenki kiválaszthatja azt, amit szívesen használna. A dátumobjektumok tárolása generikus listában történik, aminek típusa
LocalDate. A dátumok formátuma:
DateTimeFormatter.ofPattern("yyyy.MM.dd.").
1. megoldás
Ez a hagyományosnak tekinthető megoldás. Végigjárja a dátumobjektumokat tartalmazó
dateList dátumlista adatszerkezetet. Két egymásba ágyazott ciklussal csoportváltást valósít meg. Feltételezi – nem ellenőrzi -, hogy az adatok sorrendje megegyezik az eredmény kiírásának megfelelő sorrenddel. Amíg két egymást követő dátum
GROUP_BY_FORMAT formátuma azonos, addig ugyanabba a csoportba tartoznak. A csoportváltáskor az eredmény
TYPE_FORMAT formátumú. Közben a beépített megszámolás programozási tétel is működik.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
static void groupByDate1(String message, String groupText, ArrayList<LocalDate> dateList, final DateTimeFormatter GROUP_BY_FORMAT, final DateTimeFormatter TYPE_FORMAT) { System.out.println(message); int i=0; while(i<dateList.size()) { LocalDate currentDate=dateList.get(i); String typeGroup=currentDate.format(TYPE_FORMAT); String currentGroup=currentDate.format(GROUP_BY_FORMAT); int count=0; while(i<dateList.size() && dateList.get(i). format(GROUP_BY_FORMAT).equals(currentGroup)) { count++; i++; } System.out.println(typeGroup+groupText+" ("+count+")"); } } |
A
groupByDate1() függvény képes az évente és havonta csoportosítás megvalósítására. Mindez a paraméterezésén múlik. Évente csoportosít, ha így hívja meg a vezérlés:
|
groupByDate1("Csoportosítás évente", "év", dateList, DateTimeFormatter.ofPattern("yyyy"), DateTimeFormatter.ofPattern("yyyy. ")); |
Évenkénti csoportosítás során például a
2024.02.26. és a
2024.01.30. (dátumként, nem szövegként értelmezve) azért tartozik egy csoportba, mert a dátumobjektumoktól elkért év
"2024" szövegként mindkettő esetében megegyezik.
A
groupByDate1() függvény havonta csoportosít, ha így hívja meg a vezérlés:
|
groupByDate1("Csoportosítás havonta", "hó", dateList, DateTimeFormatter.ofPattern("yyyyMM"), DateTimeFormatter.ofPattern("yyyy. MMMM ")); |
Havonkénti csoportosítás során például a
2023.06.14. és a
2023.06.08. (szintén dátumként értelmezve) azért tartozik egy csoportba, mert mindkettő illeszkedik a
"202306" szövegmintára.
2. megoldás
Ez a Stream API-t és funkcionális programozást használó, újabb megoldás. Ciklus helyett beépített műveletek vannak. A
groupByDate2() függvény a dátumok évenkénti csoportosítását képes megoldani:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
static void groupByDate2(String message, String groupText, ArrayList<LocalDate> dateList, final DateTimeFormatter TYPE_FORMAT) { System.out.println(message); List<String> list=dateList.stream(). map(e -> e.format(TYPE_FORMAT)). collect(Collectors.groupingBy( Function.identity(), Collectors.counting())). entrySet().stream().sequential().map( e -> e.getKey()+groupText+" ("+e.getValue()+")"). sorted(). collect(Collectors.collectingAndThen(Collectors.toList(), e -> { Collections.reverse(e); return e; })); list.forEach((date) -> { System.out.println(date); }); } |
A
groupByDate3() függvény a dátumok havonkénti csoportosítására készült. A
YearMonth osztály beépített (
java.time csomag). A
DateCount saját POJO. Konstruktora 4 paramétert kap:
YearMonth key,
Long value,
DateTimeFormatter format és
String groupText, valamint van két hasznos metódusa. Az egyik az örökölt és felüldefiniált
toString() a formázott kiíráshoz, a másik pedig a
Comparable interfésztől implementált
compareTo() a sorrend kialakításához szükséges összehasonlításhoz.
|
static void groupByDate3(String message, String groupText, ArrayList<LocalDate> dateList, final DateTimeFormatter TYPE_FORMAT) { System.out.println(message); List<DateCount> list=dateList.stream(). map(e -> YearMonth.from(e)). collect(Collectors.groupingBy( Function.identity(), Collectors.counting())). entrySet().stream().map(e -> new DateCount( e.getKey(), e.getValue(), TYPE_FORMAT, groupText)). sorted().collect(Collectors.toList()); list.forEach((date) -> { System.out.println(date); }); } |
A funkcionális programozáshoz kötődő lambda műveletekről többször is blogoltunk már, így azokat most nem részletezem. Helyette ajánlom a szakmai blog lambda kifejezés címkéjét.
Továbbfejlesztés
Érdemes átgondolni az 1. és 2. megoldás markáns különbözőségeit, illetve egymást kiegészítő gondolatmenetét. Zárjuk két ötlettel a továbbfejlesztésre vonatkozóan:
- A 2. megoldás két függvénye megoldható egyetlen függvénnyel, amely hasonlóan paraméterezhető, mint az 1. megoldás függvénye. Ezáltal univerzális(abb)nak tekinthető megoldás is készülhetne. Aki kellően motivált és végiggyakorolja a fentieket, biztosan meg tudja oldani. Várjuk hozzászólásban, vagy az ILIAS-ban a megoldást!
- A csoportosítás egyben hierarchiát jelent, amiből fa szerkezet építhető. A fa vizuális komponensen is megjeleníthető, ahogyan blogoltunk is róla: Fát építünk.
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, a Java EE szoftverfejlesztő tanfolyam és a Java adatbázis-kezelő tanfolyam szakmai moduljának több alkalmához és az orientáló moduljának 1-4. óra: Programozási tételek alkalmához is kötődik. A Stream API-val és a lambda kifejezésekkel sokszor foglalkozunk.