Commit 138ea158 by Szeberényi Imre

split

parent c01e2352
# Állapotgép 2.0
Szorgalmi feladat __Karsa Zoltán__tól (extra pont: 4)
A állapotgépek (véges automaták) kézenfekvő megvalósítása a táblázatos megvalósítás, melynek lényege, hogy a pillanatnyi állapottól és az aktuális inputtól függően elővesszük egy táblázatból az új állapotot és az állapotátmenethez tartozó tevékenységet megvalósító függvény címét, amit végrehajtunk. Ezzel állapotgépünk algoritmusa így nézhet ki:
Ahogyan az első félévben tanultuk, az állapotgépek (véges automaták) kézenfekvő megvalósítása a [táblázatos megvalósítás](https://infoc.eet.bme.hu/ea12/#13), melynek lényege, hogy a pillanatnyi állapottól és az aktuális inputtól függően elővesszük egy táblázatból az új állapotot és az állapotátmenethez tartozó tevékenységet megvalósító függvény címét, amit végrehajtunk. Ezzel állapotgépünk algoritmusa így nézhet ki:
```
while (van_input) {
új_állapot = állapot_tábla[akt_állapot][input]
tevékenység = [akt_állapot][input]
tevélenység_vegrehajtása
akt_állapot = új_allapot
tevékenység_végrehajtása
akt_állapot = új_állapot
}
```
Így egy konkrét feladat (pl. ly számláló) implementálása csak a két táblázat megfelelő kitöltéséből áll, feltételezve, hogy mind az állapotokat, mind az inputot olyan módon kódoljuk, hogy azzal az adott nyelven lehet táblázatot (tárolót) indexelni. C nyelven az integrál típussal lehet indexelni. C++-ban már más a helyzet (ld. asszociatív tárolók).
A táblázatok (lehet összevont táblázat is) kitöltését segítheti valamilyen generátor, ekkor annak mérete nem nagyon érdekes, de lehet, hogy kézzel töltjük ki, ekkor érdemes a táblázat méretét csökkenteni. A legkézenfekvőbb csökkentési lehetőség, az állapotok összevonása és az inputot csoportokra bontása (ld. ly számláló példa).
Így egy konkrét feladat (pl. ly-számláló) implementálása csak a két táblázat megfelelő kitöltéséből áll, feltételezve, hogy mind az állapotokat, mind az inputot olyan módon kódoljuk, hogy azzal az adott nyelven lehet táblázatot (tárolót) indexelni. C nyelven integrális típussal lehet indexelni. C++-ban már más a helyzet (ld. asszociatív tárolók).
A táblázatok (lehet összevont táblázat is) kitöltését segítheti valamilyen generátor, ekkor azok mérete nem nagyon érdekes. Amennyiben kézzel töltjük ki, érdemes a táblázat méretét csökkenteni. A legkézenfekvőbb csökkentési lehetőség az állapotok összevonása és az inputok csoportokra bontása (ld. ly-számláló példa).
Az alábbiakban bemutatjuk a fenti, táblázatos módszer egy általánosított megvalósítását C++-ban. A feladat ennek megértése és felhasználásával két további állapotgéppel leírható probléma megoldása.
## Állapotgép ősosztály
Az újrafelhasználáshoz készítettünk egy `Allapotgep` ősosztályt(_allapotgep.hpp_), ami a következő template paraméterekkel rendelkezik:
Az újrafelhasználáshoz készítettünk egy `Allapotgep` ősosztályt (_allapotgep.hpp_), amely a következő sablonparaméterekkel rendelkezik:
```c++
template <
typename All, //az állapotokat kódoló típus
typename Inp, //az input csoportokat kódoló típus
typename T //az inpu típusa
typename Inp, //az inputcsoportokat kódoló típus
typename T //az input típusa
>
class Allapotgep {
//...
......@@ -30,24 +31,24 @@ class Allapotgep {
Az állapotgép a genetika szorgalmihoz viszonyítva a template paraméterezés mellett az alábbiakban tér el:
* minden állapotátmenetkor végrehajt egy akciót;
* tárolja a bevezetőben említett 2D táblázatban a következő állapotot és az akciót.
- tárolja a bevezetőben említett táblázatban a következő állapotot és az akciót;
- minden állapotátmenetkor végrehajt egy akciót.
A következő állapot és az akció tárolására heterogén kollekcióként tárolt objektumokat használunk, melynek alaposztálya a Nop osztály. Ez az osztály nem csinál semmit az _akcio_ során, csak eltárolja a következő állapotot.
A következő állapot és az akció tárolására heterogén kollekcióként tárolt objektumokat használunk, melyek alaposztálya a `Nop` osztály. Ez az osztály nem csinál semmit az _akcio_ során, csak eltárolja a következő állapotot.
```c++
struct Nop {
/// következő állapot
All kov_allapot;
Nop(All kov_all) :kov_allapot(kov_all) {}
Nop(All kov_all) : kov_allapot(kov_all) {}
/// ha az állapotgép ebbe az állapotba ér, meghívja ezt a függvényt
/// @param ch - erre az input értékre léptünk ide
virtual void akcio(T ch) { }
virtual void akcio(T ch) {}
virtual ~Nop() {}
};
```
A 2D táblázathoz egy olyan osztályt definiáltunk, ami az All és Imp sablonparaméterként megadott típusokkal indexelhető, és törli a tárol elemeket, ha elérkezett az idő.
A 2D táblázathoz egy olyan osztályt definiáltunk, amely az All és Imp sablonparaméterként megadott típusokkal indexelhető, és törli a tárolt elemeket, ha elérkezett az idő.
```c++
struct AllTabla : public std::map<All, std::map<Inp, Nop*> > {
~AllTabla() { // bejárjuk a tárolót és töröljük a dinamikusan létrehozott elemeket
......@@ -60,7 +61,7 @@ A 2D táblázathoz egy olyan osztályt definiáltunk, ami az All és Imp sablonp
}
++i1;
}
};
}
};
```
Az `Allapotgep` osztály továbbá a következő függvényekkel rendelkezik:
......@@ -84,7 +85,9 @@ All operator()(T ch) {
Nézd meg az osztályt tartalmazó _allapotgep.hpp_ fejlécfájlt!
## Lyszamlalo
Az állapotgép használatát egy egyszerű, ismerős feladattal mutatom be: Számoljuk egy szövegben az ly-ok számát! Az állapotgép így *char* bemenetet kap. Az állapotok kódolására az LyAllapot típust, az input csoportok kódolására pedig az LyInput típust vettem fel. Az számláláshoz pedig felvettem egy int számlálót (sz).
Az állapotgép használatát az első félévben már megismert feladattal mutatom be: [Számoljuk egy szövegben az ly-ok számát!](https://infoc.eet.bme.hu/ea12/#6) Gépunk állapottáblája egyszerű:
![ly-számláló](ly.png)
Az állapotgép így *char* bemenetet kap. Az állapotok kódolására az LyAllapot típust, az input csoportok kódolására pedig az LyInput típust vettem fel. Az számláláshoz pedig felvettem egy int számlálót (sz).
```c++
//https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations
......@@ -114,11 +117,11 @@ Az egyes állapotokhoz kapcsolódó akciókat a következő módon definiáljuk:
//lyszamlalo.hpp
```
Az átmeneteket pedig a következő kódrészletben adtuk meg. A táblázatból kiderül, hogy két esetben kell növelni a számlálót:
* jelenlegi állapot: *l_jott* és utána *y*-t kaptunk: +1-el növeljük, és utána *alap* állapotba lépünk
* jelenlegi állapot: *ll_jott* és utána *y*-t kaptunk: +2-el növeljük, és utána *alap* állapotba lépünk
Az átmeneteket pedig a következő kódrészletben adtuk meg. Az állapottáblából kiderül, hogy két esetben kell növelni a számlálót:
- jelenlegi állapot: *l_jott* és utána *y*-t kaptunk: +1-gyel növeljük, és utána *alap* állapotba lépünk
- jelenlegi állapot: *ll_jott* és utána *y*-t kaptunk: +2-vel növeljük, és utána *alap* állapotba lépünk
Minden más esetben nem kell tennünk semmit, csak az állapotokat kell nyilvántartani.
A többi esetben nem kell tennünk semmit, csak az állapotokat kell nyilvántartanunk. Ennek megfelelően inicializáljuk az álalpotgép tábláját:
```c++
Lyszaml::Lyszaml() : Allapotgep(LyAllapot::alap, tab), sz(0) {
tab[LyAllapot::alap] = {{LyInput::l, new Nop(LyAllapot::l_jott)},
......@@ -133,7 +136,7 @@ Lyszaml::Lyszaml() : Allapotgep(LyAllapot::alap, tab), sz(0) {
} //lyszamlalo.cpp
```
A bemenetet input csoportokká alakító függvény `Lyszamlalo` esetén. Magyarázatra nem szorul:
A bemenetet inputcsoportokká alakító függvény `Lyszamlalo` esetén. Magyarázatra nem szorul:
```c++
LyInput input(char ch) const {
if (ch == 'l') return LyInput::l;
......@@ -142,20 +145,25 @@ LyInput input(char ch) const {
} //lyszamlalo.hpp
```
_Megjegyzés: használhattunk volna enum helyett olyan osztályt, ami az érkezett karaktertől függően úgy viselkedik, mint az _LyInput_
_Megjegyzés: használhattunk volna enum helyett olyan osztályt, amely az érkezett karaktertől függően úgy viselkedik, mint az _LyInput_
Az `Lyszamlalo` **get()** függvénye visszaadja a megtalált _ly_-ok számát. Továbbá bevezetésre került egy segéd **str()** függvény is. Nézd meg a **runtests** statikus függvényhez tartozó teszteseteket!
Az `Lyszamlalo` **get()** függvénye visszaadja a megtalált _ly_-ok számát. Továbbá bevezettünk egy segéd **str()** függvény is. Nézd meg a **runtests** statikus függvényhez tartozó teszteseteket!
## Feladatok
### Kommentezés
A mintapélda alapján hozz létre egy `Komment` osztályt, ami a bemenetére érkező szabályos C programból kiszűri a /* ... */ alakú kommenteket! Feltételezzük, hogy szövegkonstansban nem szerepel "/\*", ill. "\*/" karaktersorozat. A megszűrt, komment nélküli programot az osztály **std::string get()** tagfüggvényével lehet lekérdezni.
A mintapélda alapján hozz létre egy `Komment` osztályt, amely a bemenetére érkező szabályos C programból kiszűri a /* ... */ alakú kommenteket! Feltételezzük, hogy szövegkonstansban nem szerepel "/\*", ill. "\*/" karaktersorozat. A megszűrt, komment nélküli programot az osztály **std::string get()** tagfüggvényével lehet lekérdezni.
Nézd meg a _main.cpp_-ben lévő teszteket, állítsd az _ELKESZULT_ makrót 1-re és próbáld megoldani a feladatot. A megoldás során a *komment.hpp* és *komment.cpp* fájlokban dolgozz!
Nézd meg a _main.cpp_-ben lévő teszteket, állítsd az _ELKESZULT_ makrót 1-re, és próbáld megoldani a feladatot. A megoldás során a *komment.hpp* és *komment.cpp* fájlokban dolgozz!
### Split
Hozz létre egy `Split` osztályt ami a bemenetére érkező karaktereket úgy dolgozza fel, hogy a konstruktorában megadott karakter mentén feldarabolja és **get()** függvénye egy `std::vector<std::string>` példányt ad vissza a szétválasztott elemekkel!
Hozz létre egy `Split` osztályt, amely a bemenetére érkező karaktereket úgy dolgozza fel, hogy a konstruktorában megadott határoló karakter mentén a szöveget feldarabolja, és **get()** függvénye egy `std::vector<std::string>` példányban adja vissza a szétválasztott elemeket!
A bemenet elején és a végén érkező határoló karaktereket figyelmen kívül hagyjuk. Amennyiben több határolójel érkezik egymás után, azt egy jelnek tekintjük, azat nem keletkezik üres sztring a feldolgozás során.
Nézd meg a _main.cpp_-ben lévő teszteket, állítsd az _ELKESZULT_ makrót 2-re és próbáld megoldani a feladatot. A megoldás során a *split.hpp* és *split.cpp* fájlokban dolgozz!
Nézd meg a _main.cpp_-ben lévő teszteket, állítsd az _ELKESZULT_ makrót 2-re, és próbáld megoldani a feladatot. A megoldás során a *split.hpp* és *split.cpp* fájlokban dolgozz!
## Megoldás
A Git tárolóból letölthető [https://git.ik.bme.hu/Prog2/szorgalmi_feladatok/Allapotgep_2.0](https://git.ik.bme.hu/Prog2/szorgalmi_feladatok/Allapotgep_2.0)
fájlok felhasználásával hozz létre a lokális fejlesztőkörnyezetedben egy C++ projektet! Ehhez felhasználható a *Makefile*, amiben megtalálhatók a fordítási opciók. Tervezz, fejlessz, tesztelj, majd töltsd fel a megoldást a Jporta rendszerbe!
## Beadás
Beadandó a *komment.hpp*, *komment.cpp*, *split.hpp* és *split.cpp* fájlok.
\ No newline at end of file
Beadandó a *komment.hpp*, *komment.cpp*, *split.hpp* és *split.cpp* fájlok.
ly.png

33.1 KB

/**
* @file main.cpp
* @file main.cpp
* Nem kell beadni!
*/
#include <iostream>
......@@ -22,7 +22,7 @@ ELKESZULT makro
int main() {
Lyszaml::runtests();
std::cout << "Input olvasása EOF-ig:" << std::endl;
std::cout << "Input olvasasa EOF-ig:" << std::endl;
Lyszaml ly;
char ch;
while (std::cin >> std::noskipws >> ch)
......@@ -89,4 +89,4 @@ int main() {
GTEND(std::cerr);
return 0;
}
#endif
\ No newline at end of file
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment