14. hét: állapotgépek

Czirkos Zoltán · 2019.09.05.

Állapotgépekkel és reguláris kifejezésekkel kapcsolatos laborfeladatok.

Felkészülés a laborra:

1. Megajánlott jegy és pót ZH-k

Fontos!
  • Megajánlott jegyhez a NEPTUN-ban fel kell venni a december 16-ai vizsgát. Ha megajánlott ötösöd van és ezt nem teszed meg, nem tudjuk beírni a jegyet és elveszik.
  • Pót-pót ZH-hoz és pót kis ZH-hoz pedig itt kell jelentkezni, az infopy portálon.

2. Bemenet karakterenként

Írj programot, amely a sys modul sys.stdin.read(1) függvényhívását használva karakterenként olvas be egy szöveget, majd minden beírt karakternek a karakterkódját kiírja a kimenetére! Ha pedig elfogyott a bemenet, akkor írja ki, hogy „bemenet vége”.

Figyeld meg a program működését:

  • Amíg nem nyomsz entert egy beírt sorra, nem ír ki semmit.
  • Utána viszont egyszerre a teljes sor karakterkódját. Miért van ez így?
  • Hogy lépsz ki a programból? Nem(!!!) Ctrl–C-vel! Hogy adsz neki fájl vége jelet?
  • Minek a karakterkódja a 10?

Ilyesmi kell legyen az eredmény:

hello
104
101
108
108
111
10
Bemenet vége.
Megoldás
import sys

while True:
    c = sys.stdin.read(1)
    if c == "": break
    print(ord(c))
print("Bemenet vége.")

3. Ly számláló

Alább az előadás állapotgépes példakódját látod, az ly-számlálót.

import sys

def main():
    ALAP = 1
    L_VOLT = 2
    LL_VOLT = 3
    
    szaml = 0
    allapot = ALAP
    
    while True:
        c = sys.stdin.read(1)
        if c == "":
            break
        
        if allapot == ALAP:      # szöveg kezdete
            if c == "l":
                allapot = L_VOLT
        
        elif allapot == L_VOLT:  # már volt egy l
            if c == "l":
                allapot = LL_VOLT
            elif c == "y":
                szaml += 1
                allapot = ALAP
            else:
                allapot = ALAP
        
        elif allapot == LL_VOLT: # két l volt
            if c == "l":
                pass
            elif c == "y":
                szaml += 2
                allapot = ALAP
            else:
                allapot = ALAP

    print(szaml, "darab ly volt.")

main()

Rajzold meg a forráskód alapján állapotátmeneti és tevékenységeket tartalmazó táblázatát, vagy választásod szerint az állapotátmeneti gráfot!

Megoldás
Az „ly” számláló állapot- és tevékenységtáblája
lyegyéb
alap→l_volt--
l_volt→ll_voltsz += 1, →alap→alap
ll_volt-sz += 2, →alap→alap

4. „Hejesírásreform”

Indulj ki az előző feladat kódjából!

Rajzolj egy új táblázatot, amelyben teljes egészében módosítod a tevékenységeket. Az új program feladata nem az ly-ok számlálása, hanem egy „hejesírásreform” végrehajtása. Ennek a beolvasott szöveget majdnem változatlanul kell kiírnia a kimenetre – azzal a különbséggel, hogy az ly-ok helyett j-t, a dupla lly-ok helyett jj-t kell kiírnia. Pl. lyuk→juk, gally→gajj, viszont majom→majom marad, és a kulcs, illetve a hallgat szavak is változatlanok maradnak. (Ezek a példák fontos állapotátmeneteket és tevékenységeket tesztelnek.)

Megoldás
lyegyéb
alap→l_voltki: cki: c
l_volt→ll_voltki: "j"
→alap
ki: "l", c
→alap
ll_voltki: "l"ki: "jj"
→alap
ki: "ll", c
→alap

A kiírásokat itt jól meg kell gondolni. Alapállapotban mindent kiírunk, kivétel az l betűt, mert az lehet egy későbbi ly része. l_volt állapotban bejövő y esetén kiírjuk a j-t; viszont bejövő egyéb karakter esetén az előző l-t is ki kell írni, és a mostanit is (ilyen szó: kulcs). ll_volt esetén pedig, ha bármi más jön, akkor az előző ll-t is ki kell írni (ilyen szó: hallgat).

A szövegfájloknál bevett szokás az, hogy a fájl legutolsó karaktere mindig egy újsor (\n) karakter. Ezt azonban sajnos nem mindenhol tartják be (és nem minden szövegszerkesztő tesz így).

Ha esetleg kap a fenti program egy olyan bemenetet, ahol a szöveg 'l' betűre végződik (tehát nincs újsor, de még mondat vége jel sincs a bemenet végén), akkor hibázik; a kimeneten nem jelenik majd meg ez a betű.

5. A program futtatása parancssorból

Emlékezz vissza, mit tanultál a laboron és az előadáson a programok parancssori futtatásáról, továbbá a fájlból és fájlba átirányításról!

Indíts egy parancssort, és futtasd le abból a helyesírásreform programodat! Ehhez operációs rendszertől függően a program nevét kell megadnod, vagy elé kell írnod azt, hogy python3, esetleg verziótól függően csak annyit, hogy python (próbáld ki, a te környezetedben melyik működik). Teszteld újra a programod pl. a lyuk és gally szavakkal, utána lépj ki belőle fájl vége jellel!

Végezd el a fájlból és fájlba átirányítást is! Erre is mutat példát a hivatkozott előadásrészlet. Hozz létre ehhez a jegyzettömbbel egy „ómagyar.txt” nevű fájlt, benne ly-okat tartalmazó szöveggel. Dolgoztasd ezt fel a programoddal, és irányítsd át a kimenetét az „újmagyar.txt” nevű fájlba!

Megoldás

A lényeg:

python3 hejesirasreform.py <ómagyar.txt >újmagyar.txt

6. Grep

Írj programot, amely:

  • Kér a felhasználótól egy reguláris kifejezést.
  • Aztán beolvassa a szavak.txt fájlt... (Ezt mentsd le a gépedre!)
  • ... és csak azokat a sorait írja ki, amire illeszkedett a megadott kifejezés.

Például:

Kérem a regexet: ^..vé$
kávé
kővé
tűvé

Ne feledd, a szavak beolvasásához szükség lehet a fájl kódolásának megadására: encoding="utf-8" paraméter az open() függvény számára.

Adj meg reguláris kifejezéseket, amelyekkel kilistázhatod az alábbi szavakat:

  • Amelyekben van vicc (pl. vicces, de kaviccsal is)
  • Almával kezdődnek (pl. almaecet, de simán alma is)
  • Úgy végződnek, hogy hely (pl. táborhely, zabpehely)
  • Négy betűsek, és mindkét középső betűjük n (pl. enni).
  • Hat betűsek, fru-val kezdődnek (pl. fruska).
  • Hosszú ó-val kezdődő és végződő szavak (pl. óceánjáró).
  • Két hosszú ssz szerepel bennük (pl. összevisszaság).
  • Szerepel bennük kétszer ugyanaz a négybetűs részlet (pl. tisztiszolga, rendszerszerű)
  • Hat betűből állnak, kétszer ugyanaz (pl. bonbon)
  • Hatbetűs tükörszavak (pl. lappal)
  • Hétbetűs tükörszavak (pl. találat)
Megoldás
import re

regex = input("Kérem a regexet: ")

with open("szavak.txt", encoding="utf-8") as f:
    for s in f:
        s = s.rstrip()
        if re.search(regex, s):
            print(s)
  • Vicc: vicc
  • Alma: ^alma
  • Hely: hely$
  • Enni: ^.nn.$
  • Fruska: ^fru...$
  • Óceánjáró: ^ó.*ó$
  • Összevisszaság: ssz.*ssz
  • Tisztiszolga: (....)\1
  • Bonbon: ^(...)\1$
  • Lappal: ^(.)(.)(.)\3\2\1$
  • Találat: ^(.)(.)(.).\3\2\1$

7. Időpontok I.

Adott az alábbi három lehetséges formátum, amelyben időpontokat adhatunk meg:

  • 23:17:06 – óra, perc, másodperc, mindegyik két számjeggyel;
  • 15h 09m 53s – itt is óra, perc, másodperc, mindegyik két számjeggyel;
  • 10:15 AM – itt az óra és a perc két-két számjeggyel. Ez a formátum 12 órás; 12:00 AM = éjfél, 08:00 AM = reggel 8, 12:00 PM = dél, 05:00 PM = a délután 5 óra, azaz 17 óra.

Adj meg reguláris kifejezéseket a három formátumhoz! Írj programot, amelyik folyamatosan sorokat olvas be a bemenetről, és megmondja, hogy az első, a második, vagy a harmadik formátumban van megadva az időpont! Ha esetleg egyikben sem (hibás a bemenet), akkor írd ki azt!

Kérem az időpontot: 12:34:45
Első formátum

Kérem az időpontot: 05:34 PM
Harmadik formátum

Kérem az időpontot: 05:34 PMMMM
Hibás
Megoldás
import re

while True:
    sor = input("Kérem az időpontot: ")
    if sor == "":
        break
    if re.match(r"^\d{2}:\d{2}:\d{2}$", sor):
        print("Első formátum")
    elif re.match(r"^\d{2}h \d{2}m \d{2}s$", sor):
        print("Második formátum")
    elif re.match(r"^\d{2}:\d{2} [AP]M$", sor):
        print("Harmadik formátum")
    else:
        print("Hibás")

8. Időpontok II.

Módosítsd az előző feladatban megírt reguláris kifejezéseidet úgy, hogy az egyes időpont formátumok esetén az időpontok értelmezéséhez szükséges adatokat egy zárójelezett blokk segítségével kigyűjtöd!

  • Az első két formátumban: óra, perc, másodperc számok.
  • A harmadik formátumban: óra, perc és az A vagy a P betű.

Írd ki ezeket a kimenetre!

Végül pedig definiálj egy időpont osztályt (óra, perc, másodperc) konstruktorral, és írj függvényt, amelynek paramétere a fenti három formátumok valamelyikékben adott időpont, visszatérési értéke pedig egy időpont objektum!

9. Lyuk

Fejleszd tovább a labor eleji „hejesírásreform” programot! Tanítsd meg az állapotgépednek, hogy kezelje helyesen a mondatot kezdő, nagybetűs L karaktert! Rajzold meg az új állapotátmeneti táblázatot!

Hány új állapot kell ehhez? Működik helyesen a programod, ha azt írod bemenetként, Levél? Vajon kell-e számolnod azzal, hogy mondat elejére két j-t kell írnod?

10. Mondatok nagybetűsítője

Írj állapotgépes programot, amely a beírt, csupa kisbetűkből álló szöveget úgy javítja ki, hogy minden mondat elején álló első betűt nagybetűre cseréli!

Megoldás

Mondat végét jelző írásjel után a következő betű nagybetű, de csak akkor, ha szóköz is jött. Figyelni kell, hogy az utána lévő szóköztől nem váltunk kisbetűs módra még! A gép alapállapota a nagybetűsítés, hiszen a bejövő szöveg legelső karaktere biztosan mondat elején van.

. ! ?szóköz, \negyéb
nagybetűki: cki: cki: c.upper()
→kisbetű
kisbetűki: c
→mondatvége?
ki: cki: c
mondatvége?ki: cki: c
→nagybetű
ki: c
→kisbetű