A feladatot több módon is megoldhatjuk. Az első, ami eszünkbe juthat, hogy vesszük sorban egymás után a szöveg karaktereit, és ellenőrizzük, hogy az aktuális karakter megtalálható-e a lecserélendő karakterek között. Ha igen, akkor a karakterláncokra értelmezett replace() metódussal kicseréljük ezt, és az így módosított karakterlánccal folytatjuk a vizsgálatot és szükség esetén a cserét. Ezt mutatja az alábbi modul első függvénydefiníciója.
|
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 |
# Az alábbi függvényváltozatok a megadott szöveg karaktereit a from_chars és a to_chars # közötti leképezés alapján cserélik ki. # A from_chars minden karaktere a to_chars azonos pozícióban található karakterével lesz helyettesítve. # Ezért a megadott két karakterláncnak azonos hosszúságúnak kell lennie. def replace_chars1(text: str, from_chars: str, to_chars: str) -> str: result = text for c in text: if c in from_chars: result = result.replace(c, to_chars[from_chars.find(c)]) return result def replace_chars2(text: str, from_chars: str, to_chars: str) -> str: return ''.join(c if c not in from_chars else to_chars[from_chars.find(c)] for c in text) def replace_chars3(text: str, from_chars: str, to_chars: str) -> str: mapping = dict(zip(from_chars, to_chars)) return ''.join(mapping.get(c, c) for c in text) def replace_chars4(text: str, from_chars: str, to_chars: str) -> str: mapping = dict(zip(from_chars, to_chars)) result = text for from_char in mapping: if from_char in text: result = result.replace(from_char, mapping[from_char]) return result def replace_chars5(text, from_chars: str, to_chars: str): result = text for src, dst in zip(from_chars, to_chars): result = result.replace(src, dst) return result def replace_chars6(text: str, from_chars: str, to_chars: str) -> str: return text.translate(str.maketrans(from_chars, to_chars)) |
Az első megoldás abból a szempontból nem előnyös, hogy többször végigjárja ugyanazt a szöveget. Amikor a replace() ténylegesen cserét hajt végre, új karakterláncot hoz létre, ami teljes másolással jár. Emiatt több memóriafoglalás és másolás történik, mint a többi megoldásnál.
A második és harmadik függvényben ezt úgy küszöböljük ki, hogy egy generátorkifejezéssel előállítjuk a cserélt karakterek sorozatát, amit a join() metódussal egyesítünk karakterlánccá.
A karaktercserét a második függvénynél egy feltételes kifejezéssel valósítjuk meg, melyben ellenőrizzük, hogy a szöveg soron következő karakterét le kell-e cserélni. Ha nem, akkor a generátor aktuális eleme e karakter lesz. Ha pedig igen, akkor a cserélendő karakterek sorozatában kikeressük a find() metódussal az adott karakter indexét, amivel kikérjük a helyettesítő karakterláncban a megfelelő karaktert.
A harmadik függvénynél a karaktercserét úgy végezzük el, hogy először is létrehozzuk a lecserélendő és helyettesítő karaktereket összerendelő szótárt. A generátor aktuális elemét e szótár get() metódussal kiolvasott értéke adja. Itt kihasználjuk azt, hogy a get() metódusnak lehet alapértelmezett értéket adni, ami jelen esetben a szöveg éppen soron következő karaktere. Ezzel azt érjük el, hogy ha a szöveg soron következő karakterével mint kulccsal hívjuk meg a get() metódust, akkor ha az benne van a szótárban, vagyis lecserélendő, akkor a helyettesítő karakter lesz a visszatérési érték. Ha pedig nincs a szótárban, vagyis nem cserélendő karakterről van szó, akkor az alapértelmezett értékkel tér vissza, ami önmaga.
A fenti megoldásoknak az a hátránya, hogy a szöveg karaktereinek feldolgozása Python-szinten történik, ezért hosszú szövegeknél a sok iteráció jelentős többletidőt okozhat. Ezen segítenek a negyedik, ötödik és hatodik függvényben látható megoldások.
A negyedik függvénynél a cserélendő karaktereket vesszük egymás után, amelyek száma viszonylag kevés és független a vizsgált szöveg hosszától. A szöveg karaktereit pedig nem Python-kódban járjuk végig, hanem a cserélendő karakterekhez tartozó replace() hívásokra bízzuk a teljes bejárást. De a replace() hívást is csak akkor hajtjuk végre, ha az éppen sorra vett cserélendő karakter előfordul a szövegben. Továbbá kihasználjuk a replace() azon jellemzőjét, hogy a helyettesítést a lecserélendő karakter minden előfordulására elvégzi.
Az ötödik függvényben még egyszerűbben járunk el. Vesszük a cserélendő és helyettesítő karakterpárokat, és minden ilyen párra meghívjuk a replace() metódust.
Az utolsó, hatodik megoldás a legtömörebb, ahol a karakterlánc translate(), valamint a str típus maketrans() metódusait használjuk.
Az alábiakban a funkcionális teszt mellett a futási idő mérés kódsorait és eredményét láthatjuk.
|
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 |
# TESZTEK from timeit import timeit from random import choice import string # ----- Funkcionális teszt ----- txt = 'Űrhajóban üldögélő újságíró' from_chars = 'áéíóöőúüűÁÉÍÓÖŐÚÜŰ' to_chars = 'aeiooouuuAEIOOOUUU' for replace_chars in (replace_chars1, replace_chars2, replace_chars3, replace_chars4, replace_chars5, replace_chars6): assert replace_chars(txt, from_chars, to_chars) == "Urhajoban uldogelo ujsagiro" # Az assert sikeres lefutása azt igazolja, hogy mindegyik megoldás a várt eredményt adja. # ----- Futási idő teszt ----- def test_running_times(text: str, from_chars: str, to_chars: str, *tested_functions): """Megméri és kiírja a megadott karaktercsere-függvények futási idejét. Paraméterek: text: A feldolgozandó szöveg. from_chars: A lecserélendő karakterek. to_chars: A from_chars karaktereinek megfelelő helyettesítő karakterek. tested_functions: A tesztelendő függvények. """ func_running_times = { replace_chars.__name__: timeit(lambda: replace_chars(text, from_chars, to_chars), number=100) for replace_chars in tested_functions} for func_name, time in func_running_times.items(): print('{} -> {:.3f}'.format(func_name, time)) txt = ''.join(choice(from_chars + string.ascii_letters) for _ in range(500_000)) test_running_times(txt, from_chars, to_chars, replace_chars1, replace_chars2, replace_chars3, replace_chars4, replace_chars5, replace_chars6) # Eredmény (Python 3.14): # replace_chars1 -> 3.141 # replace_chars2 -> 4.292 # replace_chars3 -> 4.470 # replace_chars4 -> 0.439 # replace_chars5 -> 0.411 # replace_chars6 -> 1.670 |
A végrehajtási időkre kapott eredmények visszaigazolják, hogy az első három, azaz a szöveg karaktereit egyenként vizsgáló megoldás lassabb futású, mint a másik három változat. Ez utóbbiak közül az ismételt replace() hívásokra épülő függvények a mérések szerint gyorsabbak, mint a translate() metódust használó.
Mivel mindhárom megoldás a CPython C-ben implementált beépített karakterlánc-műveleteire épül, a különbség valószínűleg a belső implementációk eltérő optimalizációiból adódik.
A beépített függvények és metódusok végrehajtási ideje Python-verziónként is eltérhet. Ennek oka, hogy az egyes verziókban a belső implementációk változhatnak, ezért ugyanaz a kód eltérő teljesítményt adhat. Jó példa erre a translate() metódus. Ha az ezt használó replace_chars6() függvény futási idejét megmérjük Python 3.10, 3.11, 3.12, 3.13 és 3.14 verziókkal, akkor a 3.14-hez képest a korábbi verziók nagyobb futási időket mutatnak. Python 3.12 és 3.13 esetén a translate() futási ideje már megközelíti az első három függvényét.
Ezzel szemben a replace() metódust használó függvények a vizsgált Python verziókban hasonló végrehajtási idővel futnak. Ezért a bemutatott környezetben érdemes ezeket a megoldásokat választani, és közülük is a leggyorsabbnak bizonyuló replace_chars4() és replace_chars5() függvényeket.
A replace(), translate() és maketrans() használatának és lehetőségeinek példákkal illusztrált bővebb magyarázata – a karakterláncok többi metódusainak kifejtésével együtt – megtalálható a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig e-könyv „Beépített típusok nyilvános metódusai” fejezetében a karakterláncok metódusait tárgyaló alcím alatt.