Zárójelek ellenőrzése if–elif nélkül

Írjunk egy függvényt, amely ellenőrzi, hogy egy megadott szövegben minden kerek nyitó zárójelhez tartozik-e ugyanannyi csukó zárójel, és hogy egyetlen csukó zárójel sem jelenik-e meg a hozzá tartozó nyitó párja előtt. A függvény True értékkel térjen vissza, ha ezek a feltételek teljesülnek, egyébként False értéket adjon vissza.

Például az ‘f(x) és g(x)’ szövegben a zárójelezés megfelelő hiszen a nyitó és csukó zárójelek száma megegyezik és minden csukó zárójel a nyitó párja után van. De a ‘) f(x) és g(x)’, az ‘f(x)) és g(x)’ vagy ‘f((x) és g(x)’ szövegek esetében a zárójelezés hibás.

Az ellenőrző függvényünkre egy lehetséges megoldás a következő. Egy state nevű változót hozunk létre 0 kezdőértékkel. Ezt követően vesszük balról jobbra haladva a szöveg egyes karaktereit és megnézzük, hogy az nyitó vagy csukó kerek zárójel-e. Ha nyitó, akkor a state értékét eggyel növeljük, ha csukó, akkor eggyel csökkentjük. Ha az iteráció során a state változó bármikor negatív értékű lesz, az azt jelenti, hogy a csukó zárójel előbb jön, mint a nyitó, így biztos, hogy a zárójelezés nem megfelelő. Ezért ilyenkor azonnal kilépünk a ciklusból és False értékkel térünk vissza. Ha a state nem lesz negatív az iteráció során, akkor a legvégén pozitív értékű vagy 0 lehet. Ha pozitív, akkor az azt jelenti, hogy több nyitó zárójel szerepelt a szövegben, mint csukó. Tehát a függvény csak akkor térhet vissza True értékkel, ha a state értéke 0. E függvénydefiníciót láthatjuk alább.

Ami a két feltételes elágazásban történik, azt úgy is átfogalmazhatjuk, hogy nyitó zárójel esetén +1, csukó zárójel esetén -1 értéket kell hozzáadni a state aktuális értékéhez, majd ezzel az összeggel kell aktualizálni a state értékét. Ez lényegében egy megfeleltetést jelent a zárójelek, valamit a +1 és -1 értékek között, amit egy szótárral valósíthatunk meg. Ha így teszünk, akkor a szótár get() metódusát meg tudjuk hívni a cikluson belül az aktuális karakterrel, valamint 0 alapértelmezett értékkel. E hívás eredménye három szám lehet: +1, ha az aktuális karakter nyitó zárójel, -1, ha csukó, és 0, ha az aktuális karakter se nem nyitó, se nem csukó kerek zárójel. A get() metódus eredményével módosítjuk a state értékét. Ezzel a változattal megvalósított függvény definíciója a következő:

Vajon e megoldásnak van-e haszna azon túl, hogy egy jó gyakorlás a dict típusú szótár használatára?

Igen van, mégpedig akkor, ha a függvényünket úgy akarjuk általánosítani, hogy ne csak kerek zárójelekre, hanem más zárójelfajtákra, például a szögletes vagy akár a kapcsos zárójelek ellenőrzésére is képes legyen.

Az új követelményeknek természetesen meg lehetne úgy felelni, hogy minden egyes új zárójeltípushoz újabb és újabb feltételes elágazásokat illesztünk a függvénytörzsbe. Ez a módszer azonban általában nem ajánlott, mert minden módosításnál hibát vihetünk be egy már letesztelt kódba, ezért minden módosítás után tesztelni is kellene, ami idő- és munkaigényes.

A szótárral történő bemutatott megoldás elve azonban általánosítható, és ezzel olyan kód alakítható ki, amely újabb zárójelfajta esetén nem igényli, hogy a függvény definícióján változtassunk. Az így megvalósított függvényt láthatjuk alább.

A függvény első paramétere fogadja a vizsgálandó szöveget. Második argumentumként lehet megadni a zárójelpárokat egy karakterláncban, szóközzel elválasztva. Alapértelmezetten a kerek zárójelpár szerepel. Az egyes zárójelfajtáknak megfelelően létrehozzuk a +1, -1 értéket megfeleltető szótárakat, majd ezeket egy-egy kételemű listába helyezzük, amelyek második eleme az adott zárójelfajtához tartozó állapotérték lesz 0 kezdőértékkel. Ezeket a kételemű listákat egy másik listában gyűjtjük össze.

A kód többi része az előzőekben vázolt feldolgozási logikának az adaptálása: végig iterálunk a szöveg karakterein és minden egyes karakterre megnézzük, hogy a megadott zárójelfajták nyitó vagy csukó változatának egyikével egyezik-e. Ha igen, akkor vagy növeljük, vagy csökkentjük a hozzá tartozó állapotértéket. Ha menet közben valamelyik érték negatív, akkor rögtön visszatérünk False értékkel. Ha nem, akkor a végén megvizsgáljuk, hogy minden állapot 0 értékű-e. Ha igen, akkor True lesz az ellenőrzés eredménye, egyébként False.

Megjegyzés: A bemutatott megoldás azt ellenőrzi, hogy egy adott típusú nyitó zárójelhez tartozik-e ugyanannyi csukó zárójel, és hogy egyetlen csukó zárójel sem jelenik-e meg a hozzá tartozó nyitó párja előtt. Nem vizsgálja azonban a különböző típusú zárójelek egymásba ágyazásának helyességét, ezért például a ’( [ ) ]’ karakterláncot helyesnek tekinti. Az ilyen esetek felismeréséhez verem (stack) alapú megoldás szükséges.

A cikk célja azonban nem egy teljes körű zárójel-ellenőrző algoritmus ismertetése, hanem annak szemléltetése, hogyan válthatunk ki egy nehezen bővíthető ifelif szerkezetet egy adatvezérelt megoldással. A zárójelek ellenőrzése csupán egy gyakorlati példa ennek a technikának a bemutatására.

Ebben a feladatban a dict típusú szótárobjektumnak kiemelt szerepe volt. Erről a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv „Beépített konténerobjektumok” fejezet „Szótár” alfejezetében, valamint a „Beépített típusok nyilvános metódusai” fejezet „Leképzés típusú konténerek” alfejezetében olvashatunk bővebben.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.