Zkoušel jsem test.
Původní řešení pro exclude regex prostě používalo regex na každý řádek slovníku, který splňoval dané podmínky. Čas zpracování smyčky byl cca 470ms. Byl to obyčejná exclude_str v podstatě řetězec s alternativama.
$ ./affdic-processor.py
Trvání: 48.915 ms
dump aff smyčka zápis ve smyčce - Trvání: 48.293 ms
Wrote ./lang/cs_CZ/input/SFX-J.aff (379 rules)
Load aff rules: Trvání: 48.637 ms
smyčka s exclude Trvání: 470.289 ms
Load .dic entries: Trvání: 513.712 ms
Wrote ./lang/cs_CZ/input/SFX-J.txt (65387 items)
Wrote ./lang/cs_CZ/input/SFX-J.r.txt (874 items)
Load aff rules - zápis max. dvou souborů: Trvání: 6238.581 ms
(.venv) user@Toshi:~/Skripty/Python.čeština/czech-dictionaries$
NEEFEKTIVNÍ ŘEŠENÍ, EFEKTIVITA SE ZTRÁCÍ NA DVOU ZANOŘENÝCH SMYČKÁCH for
Zkoušel jsem to nahradit za dva seznamy nebo pole a seznam, a dát tomu sofistikovanější logiku, ale smyčka s exclude ted trvá 871 ms...
~/Skripty/Python.čeština/czech-dictionaries$ ./affdic-processor.py
Trvání: 61.627 ms
dump aff smyčka zápis ve smyčce - Trvání: 52.694 ms
Wrote ./lang/cs_CZ/input/SFX-J.aff (379 rules)
Load aff rules: Trvání: 67.405 ms
smyčka s exclude Trvání: 871.273 ms
Load .dic entries: Trvání: 915.141 ms
Wrote ./lang/cs_CZ/input/SFX-J.txt (45952 items)
Wrote ./lang/cs_CZ/input/SFX-J.r.txt (425 items)
Load aff rules - zápis max. dvou souborů: Trvání: 4408.855 ms
def extract_dic_entries(self, flag: str,
exclude_prefixes: dict = None,
compiled_exclude_regex: dict = None,
exclude_superfast: bool=True,
reverse: dict = None
) -> list:
reverse = reverse or {"input": False, "output": False}
"""
Vyhledá v .dic všechny tvary s daným flagem.
Regex vybírá část před slash '/' a kontroluje, zda mezi hranatými
závorkami je zadaný flag (např. IN pro I a N).
Pro eliminaci předpon, lze doplnit grep-like filtr: např. odmítat
řádky začínající na pro|na|do|..."""
dic_path = os.path.join(self.aff_dic_dir, f"{self.lang.value}.dic")
entries = []
# pattern = re.compile(rf"^(\S+)/.*?\[{flag}\]")
"""
if exclude_str:
if exclude_superfast:
gen = (part[0] for part in exclude_str.split("|") if part)
exclude_first_letters = list(dict.fromkeys(gen))
exclude = re.compile(rf"^(?:{exclude_str})")
"""
start = time.perf_counter()
with open(dic_path, encoding='utf-8') as f:
next(f) # skip header count
for line in f:
if exclude_superfast:
break_prim = False
for p in exclude_prefixes["keys"]:
if p == line[0]:
if len(line)>1:
for sec in exclude_prefixes[line[0]]:
if sec[1] == line[1]:
if len(sec)>2 and line.startswith(sec):
if compiled_exclude_regex[p].match(line):
break_prim = True
break
else:
break_prim = True
break
else:
break_prim = True
break
else:
break_prim = True
break
# u jiných počátečních písmen spustit obecný regex přímo
elif compiled_exclude_regex['.'].match(line):
break_prim = True
break
if break_prim == True:
break
if break_prim == True:
continue
# rozdělíme na část před '/' a za '/':
# např. "být/IN" -> base="být", rest="IN"
# Pokud uživatel nezadal flag, pak zapisovat jen slovíčka bez vlajek
parts = line.split('/', 1)
if not flag and len(parts)==1:
entries.append(line)
continue
elif len(parts) != 2:
continue
base, rest = parts
# rest může obsahovat příznaky a další text, např. "IN", "PIN", "HRIN"
if flag in rest:
entries.append(base)
for _ in range(1000000):
pass
end = time.perf_counter(); elapsed_ms = (end - start) * 1000; print(f"smyčka s exclude Trvání: {elapsed_ms:.3f} ms")
return entries
HLAVNÍ ČÁST KODU:
if __name__ == '__main__':
reverse={
"input": False,
"output": False
}
# Ukázkové volání: vytvoříme procesor, načteme .aff/Shapka a zpracujeme text
proc = AffDicProcessor(Lang.CZ)
# 1) Vygeneruješ zkrácený .aff se všemi pravidly SFX‑J
proc.dump_aff_rules(RuleType.SFX, 'J', reverse=reverse)
# 2) Na základě těch samých pravidel plus .dic entries vypíšeš tvary:
# normalně i v obráceném pořadí, s použitím stejného exclude filtru
# PŮVODNÍ ŘETĚZEC:
# exclude_str="pro|na|do|u|roze?|přede?|př[ei]|po|za|vy|ode?|obe?|znovu|spolu"
# ZKUSÍM TO VYLEPŠIT:
# exclude_prefixes -NESMÍ OBSAHOVAT REGEXY
exclude_prefixes = {
'keys': ['p', 'o', 'z'],
'p': ["po", "př"], # "pro"- neotestováno
'o': ["od", "ob"],
'z': ["za", "znovu"]
}
exclude_regex_map = {
'p': ["po|přede?|př[ei]"],
'o': ["ode?|obe?"],
'z': ["za|znovu"],
'.': ["na|do|u|roze?|vy|spolu"]
}
compiled_exclude_regex = {}
for first, patterns in exclude_regex_map.items():
group = "|".join(patterns)
# ^ → match na začátku řádky
compiled_exclude_regex[first] = re.compile(fr"^(?:{group})")
proc.dump_dic_forms( RuleType.SFX, 'J',
reverse=reverse,
exclude_prefixes=exclude_prefixes,
compiled_exclude_regex=compiled_exclude_regex
)
KOD KTERÝ BYL RYCHLEJŠÍ (starý dvoujádrový notebook celeron z roku 2009)
def extract_dic_entries(self, flag: str,
exclude_str: str = "",
exclude_superfast: bool=True,
reverse: dict = None
) -> list:
reverse = reverse or {"input": False, "output": False}
dic_path = os.path.join(self.aff_dic_dir, f"{self.lang.value}.dic")
entries = []
# pattern = re.compile(rf"^(\S+)/.*?\[{flag}\]")
if exclude_str:
if exclude_superfast:
gen = (part[0] for part in exclude_str.split("|") if part)
exclude_first_letters = list(dict.fromkeys(gen))
exclude = re.compile(rf"^(?:{exclude_str})")
start = time.perf_counter()
with open(dic_path, encoding='utf-8') as f:
next(f) # skip header count
for line in f:
if exclude_str:
if exclude_superfast:
# superfast: nejprve zkontrolovat první písmeno,
# až pak volat regex jen pro podezřelé řádky
if line[0] in exclude_first_letters and exclude.match(line):
continue
else:
# bez superfast: grep‑like filtrování pro všechny řádky
if exclude.match(line):
continue
# rozdělíme na část před '/' a za '/':
# např. "být/IN" -> base="být", rest="IN"
# Pokud uživatel nezadal flag, pak zapisovat jen slovíčka bez vlajek
parts = line.split('/', 1)
if not flag and len(parts)==1:
entries.append(line)
continue
elif len(parts) != 2:
continue
base, rest = parts
# rest může obsahovat příznaky a další text, např. "IN", "PIN", "HRIN"
if flag in rest:
entries.append(base)
for _ in range(1000000):
pass
end = time.perf_counter(); elapsed_ms = (end - start) * 1000; print(f"smyčka s exclude Trvání: {elapsed_ms:.3f} ms")
return entries
# === Příklad použití ===
if __name__ == '__main__':
reverse={
"input": False,
"output": False
}
# Ukázkové volání: vytvoříme procesor, načteme .aff/Shapka a zpracujeme text
proc = AffDicProcessor(Lang.CZ)
# 1) Vygeneruješ zkrácený .aff se všemi pravidly SFX‑J
proc.dump_aff_rules(RuleType.SFX, 'J', reverse=reverse)
# 2) Na základě těch samých pravidel plus .dic entries vypíšeš tvary:
# normalně i v obráceném pořadí, s použitím stejného exclude filtru
proc.dump_dic_forms( RuleType.SFX, 'J',
reverse=reverse,
exclude_str="pro|na|do|u|roze?|přede?|př[ei]|po|za|vy|ode?|obe?|znovu|spolu" )
Je statický přístup rychlejší? Ne: 639ms.
def extract_dic_entries(self, flag: str,
exclude_prefixes: dict = None,
compiled_exclude_regex: dict = None,
exclude_superfast: bool=True,
reverse: dict = None
) -> list:
reverse = reverse or {"input": False, "output": False}
"""
Vyhledá v .dic všechny tvary s daným flagem.
Regex vybírá část před slash '/' a kontroluje, zda mezi hranatými
závorkami je zadaný flag (např. IN pro I a N).
Pro eliminaci předpon, lze doplnit grep-like filtr: např. odmítat
řádky začínající na pro|na|do|..."""
dic_path = os.path.join(self.aff_dic_dir, f"{self.lang.value}.dic")
entries = []
# pattern = re.compile(rf"^(\S+)/.*?\[{flag}\]")
start = time.perf_counter()
with open(dic_path, encoding='utf-8') as f:
next(f) # skip header count
for line in f:
if exclude_superfast:
# superfast: nejprve zkontrolovat první písmeno,
# až pak volat regex jen pro podezřelé řádky
"""
if line[0] in exclude_first_letters and exclude.match(line):
continue
"""
if line[0] == exclude_prefixes['keys_3'][0]:
if compiled_exclude_regex['p'].match(line):
continue
elif line[0] == exclude_prefixes['keys_3'][1]:
if compiled_exclude_regex['o'].match(line):
continue
elif line[0] == exclude_prefixes['keys_3'][2]:
if compiled_exclude_regex['z'].match(line):
continue
elif line[0] in exclude_prefixes['rest_keys'].find():
if compiled_exclude_regex['.'].match(line):
continue
else:
# bez superfast: grep‑like filtrování pro všechny řádky
if exclude.match(line):
continue
# rozdělíme na část před '/' a za '/':
# např. "být/IN" -> base="být", rest="IN"
# Pokud uživatel nezadal flag, pak zapisovat jen slovíčka bez vlajek
parts = line.split('/', 1)
if not flag and len(parts)==1:
entries.append(line)
continue
elif len(parts) != 2:
continue
base, rest = parts
# rest může obsahovat příznaky a další text, např. "IN", "PIN", "HRIN"
if flag in rest:
entries.append(base)
for _ in range(1000000):
pass
end = time.perf_counter(); elapsed_ms = (end - start) * 1000; print(f"smyčka s exclude Trvání: {elapsed_ms:.3f} ms")
return entries
if __name__ == '__main__':
reverse={
"input": False,
"output": False
}
# exclude_prefixes - obsahuje první znaky u třech předloh a zbýv.
exclude_prefixes = {
'keys_3': ['p', 'o', 'z'],
'rest_keys': "dnrvs"
}
exclude_regex_map = {
'p': ["po|přede?|př[ei]"],
'o': ["ode?|obe?"],
'z': ["za|znovu"],
'.': ["do|na|roze?|vy|spolu"] # "u" nelze zařadit, je ve slově "usnout"a "snout" nemá význam
}
compiled_exclude_regex = {}
for first, patterns in exclude_regex_map.items():
group = "|".join(patterns)
# ^ → match na začátku řádky
compiled_exclude_regex[first] = re.compile(rf"^(?:{group})")
# Ukázkové volání: vytvoříme procesor, načteme .aff/Shapka a zpracujeme text
proc = AffDicProcessor(Lang.CZ)
# 1) Vygeneruješ zkrácený .aff se všemi pravidly SFX‑J
proc.dump_aff_rules(RuleType.SFX, 'J', reverse=reverse)
# 2) Na základě těch samých pravidel plus .dic entries vypíšeš tvary:
# normalně i v obráceném pořadí, s použitím stejného exclude filtru
proc.dump_dic_forms( RuleType.SFX, 'J',
reverse=reverse,
# exclude_str="pro|na|do|u|roze?|přede?|př[ei]|po|za|vy|ode?|obe?|znovu|spolu"
exclude_prefixes=exclude_prefixes,
compiled_exclude_regex=compiled_exclude_regex )
Je statický přístup s str.find(line[0]) rychlejší? Ne: 848ms.
To samé co výše jen s exclude_prefixes['rest_keys'].find(line[0])
Žádné komentáře:
Okomentovat