A beadandó feladatban gyorsírógépeket fogunk modellezni. A gyorsírógépek speciális billentyűzetek, amelyekkel sokkal gyorsabban lehet a számítógépekre szövegeket bevinni, mint a hétköznapi billentyűzetekkel. Úgy működtetjük őket, hogy egyszerre több billentyűt is lenyomunk rajtuk (ezt akkordnak -- angolul chord -- nevezzük), és egy vagy több akkord sorozatát (ennek az angol neve outline, az alábbiakban szóalak) a gép szótagokká, esetenként egy vagy több szóvá fordítja. A gyorsírógépek a fordítást szótár segítségével végzik, amelynek tartalma a gép fajtájától függ. Két különböző fajta gépet fogunk implementálni. 1. A "stenotype" alapötlete az, hogy egy szótag hangalakjának megfelelő billentyűk lenyomása ad ki egy akkordot. 2. A "velotype" alapötlete az, hogy a szó írásképét visszük be, és egy akkorddal általában több betűt írunk le. A gyorsírógépek általában a szóközöket automatikusan helyezik el; a valóságban igen-igen ritka, hogy explicit kelljen kitenni a szóközt, és az alábbi programban erre nem is biztosítunk lehetőséget. A velotype esetében viszont gyakran előfordul, hogy több akkorddal kell leírni egy hosszabb szót, ekkor egy külön billentyűt kell lenyomni (az alábbi leírásban), hogy ne kerüljön szóköz az akkord után. Ugyan a stenotype is rendelkezik hasonló lehetőséggel, de ezt nem ábrázoljuk. Az alábbi programban az akkordokat String alakban ábrázoljuk (a gépfajtától függ, mit tartalmaz a String, lásd lejjebb), a szóalakok pedig List típusúak. A szóalakoknak van szöveges alakja is: ha egy szóalak az "AB", "CD-EF" és "G" akkordokból áll, akkor a szöveges alak "AB/CD-EF/G", azaz perjellel összefűzött. A lista alakú szóalakokra az alábbiakban "chords", a szövegesen megadottakra "outline" néven hivatkozunk. Az akkordokban szereplő karakterek a gyorsírógép egy-egy billentyűjét jelölik. Előfordulhat, hogy mindkét kézhez tartozik ugyanolyan nevű billentyű, ezt az akkordokban kötőjellel jelöljük: az "ABF-ARB" akkord azt jelöli, hogy a bal kézhez tartozó A, B és F jelű billentyűket, valamint a jobb kézhez tartozó A, R és B billentyűket kell egyszerre lenyomni. Az elte.shorthand.machine.iface csomagba készítsd el a ShorthandMachine interfészt. Ez az interfész írja le a gyorsírógépek képességeit, és a következő metódusok találhatóak meg benne. - void emptyDict() A gép szótárának kiürítése. - List> readDict(String filename) throws IOException A szótár betöltése a megadott nevű fájlból. A szótárban szereplő bejegyzéseket sorban kell betölteni. Ha a fájllal valami probléma lenne (pl. nem található), a metódusnak nem kell kezelnie az így adódó kivételt. Ha egymás után több szótárat is betöltünk, a korábbi bejegyzések maradjanak meg (kivéve, ha egy újonnan betöltött bejegyzés felülír egy régebbit). A metódus visszaadja a fájlban helytelenül szereplő szóalakok listáját. - String get(List chords) Visszaadja a lista alakban megadott szóalakhoz a szótár szerint hozzárendelt szöveget. Ha nincsen megfelelő bejegyzés a szótárban, akkor null-t ad. - String get(String outline) Hasonló a másik get művelethez, de ez szöveges alakú szóalakot kap paraméterként. - List> translateTextToSteno(String text) Ez a metódus egy sornyi szöveget alakít át a vele ekvivalens szóalakok sorozatára. (Több sornyi szöveg esetén minden sorra meghívhatjuk ezt a metódust.) A sor szövegében tetszőleges karakterek állhatnak, ezek közül csak az angol ábécé kis- és nagybetűit és a szóközt tartjuk meg, minden más karakter úgy tekintünk, mintha szóköz állna a helyén; a konverzió során nem teszünk különbséget a szöveg kis- és nagybetűi között. A konverzió során az így megmaradó szavakkal foglalkozunk, a szóközöknek csak tagoló szerepe van. Az üres sor (illetve az olyan sor, amelyben csak szóközök vagy szóköznek minősülő karakterek állnak) üres listára konvertálódik. A metódus (a gépfajtának megfelelő módon) előállítja a szövegből a szóalakok sorozatát, ezzel tér vissza. - String translateStenoToText(String stenoText) A bemeneti szöveg szóközökkel elválasztott szóalakokat tartalmaz. Ebből a metódus előállítja a szöveget: lefordítja a szóalakokat a szótára szerint. Az így előálló szöveg kis- és nagybetűket egyaránt tartalmazhat. - (A korábbi feladatkiírásban szerepelt itt még egy metódus: getDictSize. Ezt a metódust nem szükséges sem az interfészbe, sem az osztályokba felvenni.) Az elte.shorthand.machine csomagba készítsd el a Stenotype és a Velotype osztályokat, amelyek implementálják a ShorthandMachine interfészt. A gépek jellemzői a következők. Stenotype adatai - A stenotype két Map típusú adattagot tartalmaz: - a chordPartsToText a (lista alakú) szóalakokhoz adja meg a hozzájuk tartozó szöveges alakot - itt a szöveget nem kell kisbetűsre konvertálni - a textToChords pedig éppen fordított irányban működik - mielőtt bekerülne a textToChords-ba, a szöveget kisbetűsre kell konvertálni Stenotype metódusai - az emptyDict és a két get művelet működésének leírását lásd fent - readDict A szótárfájl felépítése a következő. - Az első sor tartalmát figyelmen kívül kell hagyni. - A további sorok a szótár egy-egy bejegyzését írják le. - Egy sor formátuma a következő: a sor első kettőspont karaktere két részre bontja a sort. - Az első részen két idézőjel között található a szóalak. - A második rész első és utolsó idézőjele között található a szóalakhoz rendelt fordítás. - Minden olyan bejegyzést tekintsünk érvénytelennek, amelyben kisbetűkön, nagybetűkön és szóközön kívül másfajta karakterek is szerepelnek. - Néhány fordítás kettőspontot tartalmaz, természetesen ezek is érvénytelenek. - Ha bármelyik sor érvénytelen (nem pontosan egy darab kettőspontot tartalmaz, vagy nincsenek meg az idézőjelek), a teljes sort hagyjuk figyelmen kívül. Mivel a szótárak JSON formátumúak, ezért kényelmesebb eszközökkel is fel lehetne őket dolgozni, azonban a feladatban elvárás, hogy a fent leírtak szerint, szövegesen kell őket kezelni. Egy konkrét szótár letölthető a https://raw.githubusercontent.com/openstenoproject/plover/master/plover/assets/dict_unsorted.json címről. (A szótárban szereplő szóalakokról itt lehet olvasni bővebben: https://sites.google.com/site/ploverdoc/home, de ennek ismerete nem szükséges a megoldás elkészítéséhez.) Ha egy már betöltött szóalakkal újra találkozunk betöltés során, akkor az új bejegyzés írja felül a régit a chordsToText adatszerkezetben. Ha olyan fordítással találkozunk, amely(nek a kisbetűsre konvertált alakja) már szerepelt, akkor azt a szóalakot rendeljük hozzá a textToChords adatszerkezetben, amely kevesebb akkordból áll; ha a régi és az új szóalak is pontosan ugyanannyi akkordot tartalmazott, akkor a szövegszerűen rövidebbet válasszuk; ha a hosszuk is megegyezik, akkor az új alakot válasszuk. - translateTextToSteno - A fordítás során a szavaknál kisebb egységekkel nem fogunk törődni. - A fordítást legfeljebb ötszavas egységekben fogjuk végezni, azaz, ha a bejövő szöveg kezdete "Once upon a time, there were three bears.", és van egy bejegyzésünk a "once upon a time there were" szövegre, akkor azt nem fogjuk figyelembe venni. (Emlékeztető: végig csak kisbetűkkel dolgozunk, és az írásjelek nem számítanak.) - Megvizsgáljuk, hogy van-e bejegyzésünk az első öt szóhoz ("once upon a time there"). Ha van, akkor ez kerül a kimenetbe, és a maradék szöveget elemezzük tovább ("were..."). Ha nincsen, akkor az első négy szót vizsgáljuk ("once upon a time"), majd, ha annak sincsen fordítása, az első hármat, kettőt majd egyet. Ha annak sincsen fordítása, akkor egy speciális akkordot illesztünk a kimenetbe, és az első szót átugorva elemezzük a folytatást ("upon a time there were..."). A speciális akkord az első szót szögletes zárójelekkel körülvéve tartalmazza; a példában, feltéve, hogy a "once" szóhoz nincsen bejegyzés, a "[once]" hiba-akkord kerül a kimenetbe (kisbetűsre konvertálva). Ha a szöveg végére értünk, elkészültünk. - translateStenoToText A szöveg szavai szöveges szóalakok, azaz outline-ok. Megkeressük a hozzá bejegyzett szöveget, és a kimenetbe ezt illesztjük. Ha nem tartozik bejegyzés a szóalakhoz (pl. "NINCSEN/HOZZA/BEJEGYZES"), akkor a kimenetbe szögletes zárójellel körülvéve kerüljön a szóalak ("[NINCSEN/HOZZA/BEJEGYZES]"). Velotype adatai - a velotype egy szótári bejegyzése általában nem önmagában alkot egy akkordot - az akkordnak három része lehet: kezdete, közepe és vége - a szótárfájlban a bejegyzések akkordrészekhez rendelnek szövegrészeket - az akkordrészben levő "-" mutatja, hogy a bal ("PRN-"), a jobb ("-LC") vagy esetleg mindkét kéz ("O-E") billentyűit használja-e - a szövegrészben levő "-" mutatja, hogy az akkord elején ("pm-"), közepén ("-lc") vagy végén ("-oe-") szerepel-e - ezek kombinációja adja a teljes akkordot és a hozzá tartozó szöveget - pl. három bejegyzés: "F-:f-", "O-:-o-", "-R:-r", ami azt jelenti, hogy a bal kéz F billentyűjének lenyomásával "f" kerül a szó elejére, a bal O billentyűvel "o" a szó közepére és a jobb R-rel "r" a szó végére; vagyis "FO-R" akkord (bal F és O, jobb R) lenyomására a "for" szöveg keletkezik - előfordulhat, hogy a kezdete-közepe-vége hármasból csak kettő vagy egy található meg egy akkordban - segédosztály: VeloDictEntry - tartalmazza az akkordrészt (chord adattag) - tartalmazza a fordítást (text) a "-" jelek nélkül - tartalmazza, hogy a bejegyzés elején volt-e "-" (isPreCont) - tartalmazza, hogy a bejegyzés végén volt-e "-" (isPostCont) - az osztály két Map-et tartalmaz adattagként - chordPartToEntry: az akkordrészhez (amiben benne van a "-" is) rendel hozzá egy VeloDictEntry-t - textToEntry: a fordításhoz (ebben is benne van a "-") rendel hozzá egy VeloDictEntry-t - ha több akkordrész is le tudja írni a fordítást, akkor a rövidebb szövegűt válasszuk - a jobb kéz "#" karaktere azt jelöli, hogy a keletkező szó "összeragad" a következővel - használata: pl. a "CO-NTS#/I-T#/U-T#/IO-N" akkordsorozat lehet a "constitution" szó megfelelője - ezt az akkordrészt érdemes speciálisan kezelni - a szótárban kötelező megjelennie: a "-#" szóalakhoz kell a "noSpace" tartalmat rendelnie egy bejegyzésnek - ha a szótárban nincsen bejegyzés "noSpace" tartalommal, váltódjon ki MissingNoSpaceException - ennek a kivételnek nem szükséges további adattag - ha a szótárban pontosan egy bejegyzés tartalma "noSpace", de nem a "-#" akkordrészhez tartozik, akkor váltódjon ki UnexpectedNoSpaceException - a kivétel wrongText adattagja tartalmazza, hogy a "-#" helyett milyen akkordrészhez rendelte a fájl a "noSpace"-et - ha a szótárban több "noSpace" bejegyzés is található, váltódjon ki MultipleNoSpaceException - a kivételben legyen egy lineNumber1 és egy lineNumber2 adattag, amelyek megadják a "noSpace" bejegyzés első és második előfordulásának sorszámát - a három fenti kivételfajtának (MissingNoSpaceException, UnexpectedNoSpaceException, MultipleNoSpaceException) legyen egy közös őse, a NoSpaceException - ennek legyen egy adattagja, filename: ez tartalmazza az elemzett fájl nevét - mind a négy kivételosztály adattagjai legyenek private láthatóságúak, és legyen az adattagokhoz getter (getFilename, getWrongText stb.), de ne legyen hozzájuk setter - mindegyik kivételosztálynak pontosan egy publikus konstruktora legyen, ami megkapja a fájlnevet és (ha tartozik hozzá) a speciális adatát Velotype metódusai - az emptyDict és a két get művelet működésének leírását lásd fent - readDict - a fájl tartalmát soronként kell beolvasni, az üres sorokat figyelmen kívül hagyva - minden fennmaradó sor pontosan egy kettőspontot tartalmaz, elöl az akkordrésszel, hátul a szövegrésszel - létre kell hozni egy VeloDictEntry példányt - ebben az akkordrészt úgy kell eltárolni, ahogy van - a szövegrész elejéről és végéről le kell venni a "-" jelet (ha van) - el is kell tárolni, volt-e az elején/végén - a VeloDictEntry példányt bele kell tenni a két Map-be - translateTextToSteno - a szöveget szavanként fordítjuk - válasszuk le a szó első néhány (legfeljebb 5) karakterét, és próbáljunk rá illeszkedő kezdő akkordrészt találni - a fennmaradó részhez hasonlóan keressünk középső akkordrészt, majd az abból fennmaradóhoz végső akkordrészt - ha még ezután is marad a szóból, akkor a kijött teljes akkordhoz tegyük hozzá a noSpaceChord-ot, és a fennmaradó szövegrészt elemezzük tovább - az illeszkedő részek kereséséhez vegyük a szó első 5 karakterét, tegyünk a végére/mindkét végére/elejére "-" jelet (attól függően, hogy kezdő, középső vagy végső akkordrészt keresünk), és nézzük meg, a textToEntry-ben található-e ilyen bejegyzés - ha nem található, akkor próbálkozzunk 4, 3, 2, majd csupán 1 karakterrel - ha 1 karakteres szöveg sem található, akkor ez az akkordrész üres lesz - példa: tegyük fel, hogy a "february" szót próbáljuk akkordokká bontani - 1. akkord - eleje: próbálkozunk a "febru-", a "febr-", a "feb-", a "fe-" kikeresésével a textToEntry-ben, de ezek közül egyik sem található; a "f-" viszont igen (a hozzá tartozó akkordrész: "F-") - közepe: a fennmaradó szöveg "ebruary", ebből sem az "-ebrua-", sem az "-ebru-", sem az "-ebr-", sem az "-eb-", sem az "-e-" nem ad találatot, így nincsen középső akkordrész - vége: sem az "-ebrua", sem az "-ebru", sem az "-ebr", sem az "-eb" nem ad találatot, viszont van "-e" (végső akkordrész: "-E") - mivel a szónak még nincsen vége, az akkord megkapja a noSpaceChord-ot, így a teljes akkord "F-E#" - 2. akkord - eleje: a fennmaradó szöveg "bruary", tehát próbálkoznunk kell a "bruar-", a "brua-", a "bru-", a "br-" kikeresésével, ez utóbbi "PKJ-"-t ad - közepe: a fennmaradó szöveg "uary", próbálkozások: "-uary-", "-uar-", "-ua-", ez utóbbit megtaláljuk ("OE-A") - vége: "ry", próbálkozások: "-ry", megtaláltuk ("-JR") - vagyis az akkord: "PKJOE-AJR" - ebből látszik, hogy tényleg "gyors" a gyorsírás: egy akkorddal 6 karaktert írtunk le (és a szóközt) - mivel a szóból nem maradt több rész, készen vagyunk, a végeredmény: february = F-E#/PKJOE-AJR - mj.: bizonyos szavakra a fenti algoritmus nem a helyes választ adja, pl. "union" = "U-N#/IO-N" adódik a helyes "U-N#/I-ON" helyett - a kezdő+középső+végső akkordrészek teljes akkorddá fűzésénél ügyelni kell arra, hogy a "-" a megfelelő helyre kerüljön benne - érdemes az összefűzés végrehajtására egy segédfüggvényt készíteni - translateStenoToText - minden egyes akkordot külön fordítunk szöveggé - ehhez vegyük az akkord szövegének első 6, 5, ..., 1 karakterét - a chordPartsToText-ben fogunk rákeresni úgy, hogy az elejére/végére "-"-t teszünk (ha esetleg nincsen ott) - pl. "A" szöveg esetén "-A"-ra, "-A-"-ra és "A-"-ra egyaránt rákeresünk, "A-" szöveg esetén csak az utóbbi kettőre, "-A" esetén csak az előbbi kettőre - ha a szöveg közepében szerepel "-", akkor csak magára az akkord szövegére kell rákeresni - példa: tegyük fel, hogy a "PKJOE-AJR" akkordot akarjuk kikeresni (a "february" második akkordját) - első keresési fázis - az első 6 karaktere "PKJOE-", ezért rákeresünk "PKJOE-"-ra és "-PKJOE-"-ra (nincsen találat) - az első 5 karakter "PKJOE", ezért rákeresünk "PKJOE-"-ra, "-PKJOE-"-ra és "-PKJOE"-ra (nincsen találat) - első 4 karakter: "PKJO-", "-PKJO-", "-PKJO", nincsen találat - első 3 karakter: "PKJ-", találat: "br-" - második keresési fázis - nincsen benne 6 karakter, ötről indulunk - első 5 karakter "OE-AJR", erre keresünk rá (mert a közepében ott a "-"), nincsen találat - első 4 karakter "OE-AJ", nincsen találat - első 3 karakter "OE-A", találat: "-ua-" - harmadik keresési fázis - "JR", keresés: "JR-", "-JR-", "-JR", az utolsónál találat: "-ry" - tehát az akkord tartalma "br-" + "-ua-" + "-ry", vagyis "bruary" - mivel az akkordban nem szerepelt a "#", utána szóközt kell tenni - mj.: az algoritmusban nem használjuk ki, hogy az akkord elemzése során tudjuk, hogy a bal vagy a jobb kéz részén járunk A tesztkimenetek ellenőrzéséhez készíts külön programot (ShorthandMachineTest), amely a következőket teszi. 1. elkészít egy-egy példányt a kétfajta gyorsírógépből, Stenotype esetében a dict_unsorted.json szótárral 2. betölti a monte-cristo.txt fájlt 3. a fájl minden sorára meghívja a translateTextToSteno műveletet, és ennek eredményét kiírja a steno-out.txt illetve velo-out.txt fájlba - az esetleges szélső szóközöket vágjuk le a kiírandó sorból 4. az átalakított sorokra meghívja a translateStenoToText műveletet, és ennek eredményét kiírja a steno-out-re.txt illetve velo-out-re.txt fájlba Tippek - mivel az interfészek még nem szerepeltek sem az előadáson, sem a gyakorlatokon, a feladat megoldásának érdemes úgy nekifogni, mintha csak a Stenotype és a Velotype osztályt kellene elkészíteni - a kód szerkezete legyen minél szebb, minden olyan funkcionalitás, ami önmagában értelmezhető, kerüljön külön metódusba - ezek a segédmetódusok mind private láthatóságúak legyenek - esetenként jól jönne, ha vissza lehetne adni több értéket is egy metódusból (pl. egy szó két szétbontott részét), erre nincsen beépített eszköz Javában - "szebb", de kényelmetlenebb megközelítés: külön osztályt lehet a visszatérési értékhez készíteni - kényelmesebb kényszermegoldás: String[] (ha több String-et kell visszaadni), Object[] (ha különböző típusú értékeket kell visszaadni) - a megoldást önállóan kell elkészíteni, nagymértékű kódegyezés esetén mindkét másoló fél megoldása el lesz utasítva - a példa a Monte Cristo grófja című könyv angol fordítását használja bemenetként - az eredeti fájl a http://www.gutenberg.org/cache/epub/1184/pg1184.txt címről tölthető le - a steno-out.txt és a velo-out.txt a translateTextToSteno művelet soronkénti alkalmazásával készült - a steno-out-re.txt és a velo-out-re.txt az így elkészített fájlokra alkalmazza soronként translateStenoToText műveletet - az elkészülő programnak (betűre) pontosan ezeket a kimeneteket kell adnia a tökéletes megoldáshoz - a steno szótár: https://raw.githubusercontent.com/openstenoproject/plover/master/plover/assets/dict_unsorted.json - a velo szótárat a velo-rules.txt tartalmazza