From 79c83226910e8f3b6c0ba3d8b186c2f0853fc4e4 Mon Sep 17 00:00:00 2001 From: Pearu Pung Date: Thu, 3 May 2018 03:16:03 +0300 Subject: [PATCH 1/2] EX10 initial commit The step by step solution to EX10Stargate, an exercise given in accordance to ITI0202, the main programming course in TUT lectured by Ago Luberg. This page is meant to be thorough and updated and written in parts in the course of a few days. --- Solutions/EX10 | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 Solutions/EX10 diff --git a/Solutions/EX10 b/Solutions/EX10 new file mode 100644 index 0000000..ed9a269 --- /dev/null +++ b/Solutions/EX10 @@ -0,0 +1,158 @@ +EX10Stargate lahendusjuhend +=========================================================== +(ehk kuidas ehitada planeete ja siis mõttetumad ära visata) +----------------------------------------------------------- +EX10Stargate'i lahendamisel tuleb kindlalt kasuks +PR10-e läbitegu, kuid siin las olla vaid EX10Stargate'i +ülesande juhend. Kui mõni samm tundub liiga lihtne, siis +võib selle julgelt ise ära teha. Siit aga leiab ühe suuremat +sorti rõõmusõnumi ehk evangeeliumi: päris kindlasti leiavad +juhendist kõik väga põhjaliku ülevaate lahendusest ja algajad palju +abi, sest küsija suu pihta ei lööda: kui paned Githubis +issue külge, täiendatakse loodetavasti juhendit vastavalt. +Hõissa ja laulupidu! + +Mooduli ja pakkide ülesseadmine ehk korista enne segaduse teket +--------------------------------------------------------------- +Selleks, et ülesannet alustada, klõpsame parema hiireklahviga +mõne mooduli peale, tekkinud valikmenüüs valime New->Module, +vaatame tekkinud aknas, et me looks Java mooduli, mis kasutab +mõnda juba paigaldatud Java JDK-d. Vajutame Next ja muudame +kausta nimeks EX10Stargate ja vajutame Finish. IntelliJ koostab +ise projekti uue mooduli ja paneb sinna allikkausta src meile +kasutamiseks. Milline rõõm! + +Edasi tuleb luua pakk ee.ttu.iti0202.stargate klõpsates +src kausta peal parema hiireklahviga ja valides valikmenüüs +New->Package. Sisesta ee.ttu.iti0202 paki nimeks ja loo +samamoodi pakile kaks alampakki: planet ja space, seekord +ee.ttu.iti0202.stargate paki peal klõpsates. Siia lähevad +meie ülesande klassid. Nüüd on meil kõik olemas, et hakata +klasse lisama. Juhhei! + +Klass Planet ehk teeme kodus ise oma planeedi, tuleb odavam +----------------------------------------------------------- +Klass Planet hoiab endas järgmist infot: planeedi nime (sõne), +elanikearvu (long ehk ülipikk täisarv), kas planeedil on +tähevärav (tõeväärtus), kas too tähevärav on kasutatav +või lihtsalt vanaraud (ehk kas DHD on olemas - veel üks +tõeväärtus) ja mis tiimid teda külastanud on (järjend sõnedest). +Tegu on ülestuunitud järjendiga: selle asemel, et hoida ühe +planeedi andmeid lihtsas listis, paneme nad meie loodud klassi, +millel on rohkem võimalusi, šarmi ja isikupära. On mugavam +käidelda andmeid, kui IntelliJ pakub muutujanimesid puuvilja- +vaagnal, kui et peab meeles hoidma, mis indeksiga on mingi +kirje järjendis. Kuis metsa hõikad, nõnna vastu kajab! + +Selle jaoks paremklõpsame pakil planet ja valime New->Java class. +Nimeks paneme Planet ja juba saame Planet klassi sisse +kirjutada viis muutujat: + private String name; + private long inhabitants; + private boolean stargateAvailable; + private boolean dhdAvailable; + private List teamsVisited; +Nüüd toimime järgnevalt: paremklõpsame koodiredaktori aknal +kuskil klassi sees ja valime Generate või olles kursoriga klassis +vajutame Alt+Insert. Uues menüüs valime Getter and Setter ja tekkinud +aknas valime kõik muutujad vajutades Shift ja nooleklahve. +Klikkame OK ja tekivad ülesandes vajaminevad getter ja setter +meetodid. Kuid see pole veel kõik! Samamoodi klõpsates ja +valides Generate ja Constructor, saame valida tekkinud aknas +kõik muutujad, vajutada OK ja äkitsi on meil konstruktor, +mis klassis samuti vajalik oli. Nüüd on vaja vaid kirjutada nendele funktsioonide +alla string, valida IntelliJ pakutud public toString() +funktsioon, panna funktsioon planeedi nime tagastama ja +klass on tehtud. Halleluuja! Kristus sündinud meil! + +Klass PlanetBuilder, sest allhanked on kasvava majanduse alus +-------------------------------------------------------------- +Kui ütled PlanetBuilder, siis mõtled StringBuilder aga +planeetidega! Hetkekski ei tohiks olla kõhklust, mida PlanetBuilder +teeb -- ta ehitab planeete! Aga milleks meil siis Planet klassi +konstruktor? Teeb ta ju sama hästi planeedi valmis? Vastus on, +et kuigi IntelliJ ütleb mõnikord ette, mis parameetrid tuleb +klassis algväärtustada, siis ausalt öeldes on see mõnikord tüütu +ja kaka-pähh. Ja mida teha siis, kui tahad andmeid lisada omas +tempos, siis kui *sulle* sobib? Niisiis loome klassi, mis aitab +meil sutsu kergemalt hingata ja selgesti nimetatud funktsioonidega +lisada andmeid ja mis tagastab meile planeedi objekti siis, kui +*meie* seda soovime. Emantsipatsioon võidule! + +Paremklõpsatus koodil (aga mitte näiteks String märksõnal, proovi parem +mõnd tühja rida selleks) ja valik Refactor->Replace Constructor with +Builder toob meie ette akna, mis täidab kõik me unistused. +Tekkinud aknas võtame veerus Setter Name kõikidelt muutujanimedelt +set eest ära ja teeme algustähe väiketäheks, sest ülesanne nõuab nii +ja vajutame Refactor. Tekib uus klass PlanetBuilder, mille objekti +saame luua, anda funktsioonidega sisse erinevaid väärtusi ja luua +Planet objekt funktsiooniga createPlanet(). Nõnnasi saame mängida +jumalat ja luua planeete oma elutoadiivanilt. + +Klass Space ehk ülekosmoseline Rajaleidja sõnarohkete funktsioonidega +------------------------------------------------------------------------------ +Kosmos on suur kant ja tal on palju planeete, algaja võibolla ei teagi, millisele minna. +Selleks on vaja neid sortida ja ülesande kirjelduses on kriteeriumid selgelt kirjas. +Järgnevalt on loetelu neist kõigist, võime nendest silmad üle libistada, sest võtame nad hiljem +niikuinii üksipulgi lahti. + +Selleks, et orienteeruda suures komsoses on meil vaja 7 +funktsiooni: üht, mis tagastab järjendi klassile teada-tuntud planeetidest, +teist, mis saab CSV (Comma separated values) vormis failst kätte plnaaetide andmed, +kolmandat, mis tagastab hulga planeetidest, kus pole elanikke, neljandat,mis annab hulga +planeete, mida saab külastada, aga pole veel külastatud, viiendat, mis tagastab keskmise +täheväravaga planeedi elanike arvu, kuuendat, mis tagastab tiimid, kes on külastanud vähe- +asustatuid planeete, ja seitsmendat, mis tagastab planeetide tüübi koos nende sagedustega. +Kuna tööd on parajalt, siis hakkame aga pihta. Tahad latva ronida, hakka tüvest peale! + +List getPlanets() ehk kui peaksid tahtma kõiki planeete uurida ja külastada +----------------------------------------------------------------------------------- + +On ilmselge, et planeetide tagastamiseks järjendina võiks luua instantsimuutuja, mis +kätkeb endas planeetide järjendit. Niisiis loome ja algväärtustame selle tühjaks järjendiks, +et ei tekiks määramata väärtuse erindit: + private List planets = new ArrayList<>(); +Nüüd pole miskit, kui seada üles getter meetod nii, nagu seda teinud juba oleme. Kes tasa +sõuab, see kaugele jõuab. + +List csvDataToPlanets(String filePath) ehk teiste töö ärakasutamine autorile viitamata +---------------------------------------------------------------------------------------------- +Selleks, et saaksime andmeid töödelda ja esitada, peame tundmatu autori CSV-formaadis andmed vormima ümber +Planet objektideks, esitama neid enda tehtu pähe, salvestama nad ja paluma jumalat, et nood andmed on +usaldusväärsed ja vigadeta. Niisiis peame tegema vähe failist lugemist ja planeediloomet. Lisaks hakkame +esimest korda kogu ülesande jooksul kasutama Stream API-t, niisiis en guarde, noor koodija! + +Kõigepealt aga loeme failist kõik andmed ja paneme need muutujasse: selleks kasutame +Files.readAllLines(Path path) funktsiooni, mis mugavalt tagastab järjendi faili ridadest, millele path +parameeter viitab. Selleks aga, et parameeter filePath järsku Pathiks muutuks, kasutame järgmist koodirida, +mille leidsime Java dokumentatsioonist: + Path path = Paths.get(filePath); +Edasi valime ühe muutujanime, millesse salvestame kõik andmeread, näiteks lines. Nota bene! Faili näidisest järeldame, et esimene +rida sisaldab alati veeru päised, ehk siis metaandmeid, mis meile on silmaga kontrollimisel küll tulusad, +aga mida töödelda on asjatu. Niisiis jätame välja tolle esimese rea: + List lines = Files.readAllLines(path).sublist(1, lines.size()); +IntelliJ Java kompilaator aga pistab röökima selle peale, sest, nagu kõik head programmid, ta usaldab vähe kasutaja hooleks, +veel vähem usaldab kasutajat. Sellel real võib tekkida sisend-väljund erind (InputOutputError), mis võib +tulla sellest, et pole faili või faili ei saa avada (pole õigusi) või fail on kuskil mujal või universum +tahab sulle öelda, et sa võiksid lahendada teised meetodid kõigepealt ära ja minna selle konkreetse reaga +Ago juurde konsulli. Kuna aga Java tahab kindlalt koodiga edasi minna, olgu mis tahes, siis, olles kursoriga +tol real, vajuta Alt-Enter ja vali Surround with try-catch. IntelliJ genereerib koodi, mis Java kompilatori +maha rahustab, ja kõik on jälle korras. Kas pole mitte tore? + +Nüüd on aeg kasutada Java vooge (Stream API-t). Teeme ridade järjendist voo, paneme voole vahemeetodi map(), kus +lisame hiljem andmed omatehtud andmestruktuuridesse ja kogume voo lõpus lisamisel tagastatu järjendisse, salvestades +kogutu intsantsimuutujasse, mille tegime eelmist funktsiooni tehes: + planets = lines.stream() + .map( + // Meetod, mis töötleb ühe rea andmeid, teeb neist PlanetBuilderiga ühe Planeti + ) + .collect(Collectors.toList()); +Meenutame, et .stream() tegi andmestruktuurist tema elementide voo, .map() töötleb igat voo elementi vastavalt +argumendiks antud funktsioonile ja .collect() kasutab argumendiks antud meetodit, mis kogub elemendid kokku uueks +andmestruktuuriks. + +Edasi peame kirjutama igat rida töötleva koodi. Kasutame selleks lambdat ja PlanetBuilderit. + +# varsti kirjutan edasi + + From ef25d2cfc4240de7ae6c8938d87e8f9442ac35f2 Mon Sep 17 00:00:00 2001 From: Pearu Pung Date: Mon, 4 Jun 2018 11:58:34 +0300 Subject: [PATCH 2/2] First draft complete --- Solutions/EX10 | 139 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/Solutions/EX10 b/Solutions/EX10 index ed9a269..4687e58 100644 --- a/Solutions/EX10 +++ b/Solutions/EX10 @@ -151,8 +151,143 @@ Meenutame, et .stream() tegi andmestruktuurist tema elementide voo, .map() töö argumendiks antud funktsioonile ja .collect() kasutab argumendiks antud meetodit, mis kogub elemendid kokku uueks andmestruktuuriks. -Edasi peame kirjutama igat rida töötleva koodi. Kasutame selleks lambdat ja PlanetBuilderit. +Edasi peame kirjutama igat rida töötleva koodi. Kasutame selleks lambdat ja PlanetBuilderit. Alustame lambdaga: + + line -> { // töötlev kood} + +Seal sees peame rea jaotama sõnemassiiviks split() meetodiga, kasutame poolitajana koma, käivitame +uue PlanetBuilderi klassi, paneme nimeks nullinda elemendi sõnemassiivist (sest esimene +veerg hoiab endas planeedinime), elanike arvuks esimese, tähevärava olemasoluks teise, ja +DHD olemasoluks kolmanda. Neljanda väljaga, milleks on külastanud tiimide nimed, tuleb +rohkem tööd teha, seal peab vaatama, et ta oleks rohkem kui 2 tähemärki pikk (ehk siis +ei ole lihtsalt tühjad nurksulud), jaotama ta sõnemassiiviks split() funktsiooniga, kuid +seejuures ei tohi kaasa võtta esimest tähemärki ja viimast, sest nad on nurksulud ja +järjendi tähiseks, mitte ühegi planeedi nime osa. Viimaks, kui oleme lisanud kõik väljad, +loome planeedi ja tagastame selle lambdas. Seega oleks kogu töötlev kood selline: + line -> { + String[] lineData = line.split(","); + PlanetBuilder planetBuilder = new PlanetBuilder(); + planetBuilder.name(lineData[0]); + planetBuilder.inhabitants(Long.parseLong(lineData[1])); + planetBuilder.stargateAvailable(Boolean.parseBoolean(lineData[2])); + planetBuilder.dhdAvailable(Boolean.parseBoolean(lineData[3])); -# varsti kirjutan edasi + String teamsVisited = lineData[4]; + if (teamsVisited.length() > 2) { + planetBuilder.teamsVisited(Arrays.asList(teamsVisited.substring(1, teamsVisited.length() - 1) + .split("; "))); + } else { + planetBuilder.teamsVisited(new ArrayList<>()); + } + + Planet planet = planetBuilder.createPlanet(); + planets.add(planet); + return planet; + } + +Viimaks tagastame csvDataToPlanets() funktsioonis planeedid, mida kogusime: + +return planets; + +getDeadPlanets(List planets) ehk potentsiaalsed klassiekskursiooniideed +------------------------------------------------------------------------------- + +Siin hakkame kasutama Stream API-d. Kõigepealt loome planeetidest voo: + + planets.stream() + +Filtrime välja need planeedid, kus pole elanikke, aga kuhu saab reisida: + + planets.stream() + .filter(planet -> planet.getTeamsVisited().size() == 0 + && planet.isDhdAvailable() && planet.isStargateAvailable()) + +Ja kogume nad kokku hulgaks ja tagastame ta: + + return planets.stream() + .filter(planet -> planet.getTeamsVisited().size() == 0 + && planet.isDhdAvailable() && planet.isStargateAvailable()) + .collect(Collectors.toSet()); + + +getAvgInhabitantsOnPlanetsWithStargate(List planets) ehk statistiku maiuspala +------------------------------------------------------------------------------------- + +Siin me peame samamoodi sõela peale saama planeedid, kuhu on võimalik reisida, ja võtma +nende elanike arvu ja tagastama kõigi planeetide keskmise elanike arvu. Võtmiseks sobib +mapToLong() funktsioon, millel on lambda-funktsioon sees, mis tagastab planeedi elanike +arvu, ja keskmise leidmiseks sobib .average() funktsioon. Nii tuleb funktsiooni sisuks: + +return planets.stream().filter(planet -> planet.isStargateAvailable()) + .mapToLong(planet -> planet.getInhabitants()).average(); + + +getTeamsWhoHaveVisitedSmallNotDeadPlanets(List planets) ehk keda tunnustada äärealase tegevuse eest +----------------------------------------------------------------------------------------------------------- + +Siin alustame samuti sõelumisega: tahame sõela peale saada planeedid, mille elanike arv +on väiksem kui 2500, aga suurem kui 0. Peale seda peame tiimide järjendite voo muutma +tiimide vooks. Niisiis näeb meie koodirida välja nüüd selline: + + planets.stream().filter(n -> n.getInhabitants() < inhabitantLimit && n.getInhabitants() > 0) + .flatMap(n -> n.getTeamsVisited().stream()) + +Siia lisandub see nõue, et me peame kordustest lahti saama, selleks sobib .distinct(): + + planets.stream().filter(n -> n.getInhabitants() < inhabitantLimit && n.getInhabitants() > 0) + .flatMap(n -> n.getTeamsVisited().stream()) + .distinct() + +Ülesandes on vaja too tiimide voog ära sortida, niisiis peame kasutama .sorted() funktsiooni. +Samas me ei saa puhtalt terve tiimi nime põhjal sortimist teha, peame kasutama numbrilist osa. +Selleks peame tegema võrdleja, ehk siis meetodi, mis tagastab positiivne täisarvu, kui +esimene objekt on teisest suurem, nulli, kui objektid on võrdsed, või negatiivse täisarvu, +kui teine on esimesest suurem. Lisandub kood: + + .sorted((o1, o2) -> { + int first = Integer.parseInt(o1.substring(3, o1.length())); + int second = Integer.parseInt(o2.substring(3, o2.length())); + if (first > second) { + return 1; + } else if (first < second) { + return -1; + } else { + return 0; + } + }) + +Nüüd kogume tulemused kokku ja tagastame need: + + return planets.stream() + ... + .collect(Collectors.toList()); + + +getCodeNameClassifierFrequency(List planets) ehk vaatame planeetide seisu +--------------------------------------------------------------------------------- + +Siin peame planeetide voost sõeluma välja need, mis ei vasta ülesandes kirjeldatud mustrile. +Üks viis seda teha on poolitada planeedi nimi tolle mustri järgi ja vaadata, kas tekkinud +sõnemassiiv on võrdne tühja sõnemassiiviga. Viimaseks sobib staatiline meetod Arrays.equals(), +esimeseks on tarvis sutsu regulaaravaldisi tunda: kui meil on esimene täht P, millele +järgneb kaks numbrit või tähte, millele järgneb sidekriips ja numbririda, siis sel puhul +tuleb järgnev kood kasuks: + + .filter(n -> Arrays.equals( + n.getName().split("P\\p{Alnum}\\p{Alnum}-\\p{Digit}*"), new String[]{})) + +Sealt edasi peame ta koguma Mapiks, niisiis peame kasutama üht erilist kollektorit: +Collector.groupingBy(). Meie juhul anname talle kaks argumenti: esiteks funktsiooni, +mis tagastab võtme, millega ta Mapis toimtama hakkab, ja funktsiooni, mis täpsustab, +mida väärtusega teha. Esimeseks sobib lambda, mis tagastab planeedi identifikaatori, +teiseks sobib Collector.summingLong() funktsioon, mille sees on funktsioon, mis tagastab ühe. + +.collect(Collectors.groupingBy(planet -> planet.getName().substring(0, 3), + Collectors.summingLong(e -> 1))); + + + Sellega on ülesanne lahendatud ja saab minna konsultatsiooni kaitsma! + +