Ahogy a természetes nyelveknél a készségszintű tudáshoz csak a rendszeres aktív gyakorlás vezet, ugyanez igaz a programozási nyelvek tanulására is. Nem elég csak olvasni a nyelv szabályait és kész kódokat, hanem írni is kell azokat, minél többet. Nyilván, ha valakinek ez a foglalkozása, akkor ez megvalósul, de azoknak, akik a nyelvet tanulják és olyan a napi elfoglaltságuk, hogy nem kell újabb és újabb programot fejleszteni, azoknak ajánlatos, hogy maguk találjanak ki feladatokat és oldják meg előbb elvi szinten, és utána az adott programozási nyelven kódolva.
Először mindig célszerű egyszerű dolgokkal kezdeni, hogy a megoldás sikerélménye az egyre nehezebb feladatokra ösztönözzön. Ha esetleg nincs ötletünk arra, hogy milyen feladatot oldjunk meg, akkor az is jó módszer, ha megpróbáljuk a Python által készen nyújtott függvények, metódusok vagy osztályok saját készítésű változatát létrehozni. A cél tehát az, hogy funkcionálisan ugyanazt valósítsa meg a mi definíciónk, mint a készen rendelkezésre álló.
Ennek a módszernek az egyik előnye, hogy a megoldásunk helyessége könnyen ellenőrizhető. Másrészt, meg fogjuk látni, hogy egy ilyen feladat megoldásához is nem kevés elméleti és nyelvi ismeretet kell, ami ezáltal aktívvá teszi az eddig már megtanultakat. A szinte elkerülhetetlen hibázások, és azok javítása pedig ezt csak tovább mélyítik.
Ennek szellemében gyakorlásképpen utánozzuk le mondjuk a karakterláncokra meghívható find() és count() metódusokat, vagyis írjunk egy-egy függvényt, amelyek ugyanazt tudják, mint ezek a metódusok.
Azt, hogy ezek a metódusok pontosan mit csinálnak, milyen argumentumokat fogadnak és mi a visszatérési értékük a Python hivatalos dokumentációjában, vagy magyarul és kicsit bővebben kifejtve 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önyvben példákkal együtt megtalálható. De röviden összefoglalva, a céljuk és használatuk a következő:
Ha egy text nevű változó egy karakterláncot azonosít, akkor a text.find(sub, start, end) metódus a sub argumentummal megadott karaktersorozat első előfordulásának indexével tér vissza. A keresés a text kezdetétől indul. Ha nem található a szövegben a sub, akkor -1 lesz a visszaadott érték.
A text.count(sub, start, end) metódus a find()-hoz hasonló argumentumokat fogad. Visszatérési értéke a sub karaktersorozat előfordulásainak a száma.
Mindkét metódusnál a start és end opcionális argumentumokkal a keresést korlátozhatjuk a kezdő és vég indexek közé. Ha ezek nincsenek megadva, akkor a teljes text karakterláncban történik a keresés.
Mivel a start és end paramétereknek negatív indexet is meg lehet adni, a saját függvényeink készítéséhez nem árt, ha visszaidézzük, hogy sorozatokra hogyan működik a negatív indexelés. Ebben segít az alábbi ábra.

A következőkben a megvalósított find() és count() függvények definícióit látjuk. A kommentek segítik a működés megértését.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# Az alábbi függvények a str.find() és str.count() metódusok viselkedését utánozzák, kivéve azt, amikor # a keresendő kifejezés üres karakterlánc. def find(s: str, sub: str, start: int | None = None, end: int | None = None) -> int: """Azt a legkisebb indexet adja vissza, ahol a sub előfordul az s[start:end] szeletben. ValueError hibát dob, ha a sub üres karakterlánc, és -1-et ad vissza, ha a sub nem található. """ if not sub: raise ValueError("A keresendő karakterlánc nem lehet üres.") # A megadott kezdő- és végindexeket normalizáljuk. def normalize_index(index: int) -> int: """A megadott indexet normalizálja a 0 és len(s) közötti tartományra.""" if 0 <= index <= len(s): return index # Ha az index negatív, először megpróbáljuk nemnegatív indexszé alakítani úgy, hogy # hozzáadjuk a len(s) értékét. Ezután: # - ha továbbra is negatív, akkor 0-ra állítjuk. # - ha nagyobb, mint a len(s), akkor len(s)-re állítjuk. if index < 0: index += len(s) if index < 0: index = 0 elif index > len(s): index = len(s) return index _start = 0 if start is None else normalize_index(start) _end = len(s) if end is None else normalize_index(end) # A fenti normalizálás rövidebben, bár kevésbé olvashatóan a következőképpen is kifejezhető: # _start = 0 if start is None else min(len(s), max(0, start if start >= 0 else len(s) + start)) # _end = len(s) if end is None else min(len(s), max(0, end if end >= 0 else len(s) + end)) # Az s indexein addig megyünk végig, amíg a sub hosszúságú szelet teljes egészében belefér a # keresési tartományba. Éppen ezért a range() függvény stop argumentuma _end - len(sub) + 1. for i in range(_start, _end - len(sub) + 1): _slice = s[i:i + len(sub)] # Ha a szelet egyezik a kerestett karakterlánccal, akkor az aktuális indexet adjuk vissza. if _slice == sub: return i # Ha a megadott index-tartományon belül nincs egyezés a keresett karakterlánccal, akkor -1-et adunk vissza. return -1 def count(s: str, sub: str, start: int | None = None, end: int | None = None) -> int: """Az s[start:end] tartományban a sub karaktersorozat átfedés nélküli előfordulásainak számát adja vissza. Ha a sub nem található, 0 a visszatérési érték. Ha a sub üres karakterlánc, ValueError kivételt dob. """ match_count = 0 # Aktuális értéke az eddigi találatok számát jelzi. # Addig folytatjuk a keresést a kezdő- és végindex között, amíg a keresett karakterlánc előfordul. # A kereséshez felhasználjuk az egyéni find() függvényt. i = start # A keresés kezdőindexét a megadott tartomány elejére állítjuk. while (i := find(s, sub, i, end)) != -1: # Ha van találat, növeljük a találatszámlálót. match_count += 1 # A következő kezdőindexet nem eggyel, hanem a keresési karakterlánc hosszával növeljük azért, hogy # az egymást átfedő előfordulások ne kerüljenek beszámításra. i += len(sub) return match_count |
A függvénytörzs kódja egyik esetben sem túl bonyolult, mégis számos dolgot, nyelvi elemet és szerkezetet kell még ilyen esetben is ismerni, hogy megtervezzük és előállítsuk a megfelelő kódot: használtunk feltételes kifejezést, for- és while-ciklust, értékadó kifejezést, feltételes elágazást, szeletképzést, kiterjesztett értékadást és kivételkezelést. De mindezek előtt el kell gondolkodni a feladat megoldási módján, algoritmusán. Valójában ez határozza meg, hogy utána milyen nyelvi eszköztárat vetünk be a probléma megoldásához.
A függvényeink működését úgy teszteljük le, hogy bizonyos kezdő- és végindexet megadva összevetjük a visszatérési értéküket a megfelelő metódus visszatérési értékével. Ha ezek eltérnek, akkor a függvényünk nem megfelelő, és hibajelzést kapunk.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# TESZT texts = ['', 'a', 'aaaa', 'abcdef_dehi_depq'] # Azok a karakterláncok, amelyekben keresünk. patterns = ['a', 'aa', 'e', 'de', 'de' * 100] # A keresni kívánt karaktersorozatok. for text in texts: for pattern in patterns: for start_index, end_index in [(None, None), (3, None), (4, None), (5, None), (8, None), (-7, None), (None, 4), (None, 5), (None, 8), (None, 20), (5, -7), (0, 0), (1, 1), (-16, -1), (-1, -16), (-160, -1), (0, 160)]: assert text.find(pattern, start_index, end_index) == find(text, pattern, start_index, end_index) assert text.count(pattern, start_index, end_index) == count(text, pattern, start_index, end_index) # Ellenőrizzük, hogy üres keresendő karakterlánc esetén a ValueError kivétel dobódik. try: find('abcdef', '', None, None) except ValueError: pass else: assert False try: count('abcdef', '', None, None) except ValueError: pass else: assert False # Eredmény: nincs hibaüzenet, tehát az egyéni függvények minden tesztelt esetben ugyanazt az eredményt # adják, mint a beépített metódusok. |
Mivel a tesztesetekre nem kaptunk hibajelzést, ezért úgy tűnik, hogy a függvényeink az elvárások szerint működnek.