diff --git a/bin/shorten-schools b/bin/shorten-schools index 62ebc3435ee882624d6b74e3bef82ec80260cfce..6f19ce365521c0b26382905f04449ecafa1c7f54 100755 --- a/bin/shorten-schools +++ b/bin/shorten-schools @@ -1,10 +1,28 @@ #!/usr/bin/env python3 -# Zkrátí v databázi oficiální dlouhá jména škol na něco čitelnějšího, uloží -# do sloupce places.name +""" +Zkrátí v databázi oficiální dlouhá jména škol na něco čitelnějšího, uloží +do sloupce places.name. + +Algoritmus se jména snaží dostat do podoby ZKRÁCENÉ_JMÉNO, kde +ZKRÁCENÉ_JMÉNO = NÁZEV MÍSTO +NÁZEV = např. "SŠ", "ZŠ T. G. Masaryka", "SPŠ strojnická a SOŠ profesora Švejcara" +MÍSTO = MĚSTO [ULICE [Č.P.]] + např. "Slatinice", "Praha 7", "Olomouc, Svatoplukova" + +Může existovat víc možností zkrácení, např. + ZŠ a MŠ Olomouc, Svatoplukova 11 + ZŠ a MŠ Olomouc, Svatoplukova + ZŠ a MŠ Olomouc +Algoritmus vytvoří všechny varianty jmen a pak kontroluje, jestli při použití +nejkratší varianty ("ZŠ a MŠ Olomouc") nenastane konflikt jmen s jinou školou. +Pokud ano, zkusí použít pro obě školy delší variantu názvu. Toto se opakuje, +dokud se konflikty nevyřeší. +""" import copy import random import re +import sys from sqlalchemy.orm import aliased @@ -26,10 +44,11 @@ schools = [] for school, place, parent_place in schools_q: + # Parent má být škola assert parent_place.level == 3 - n = place.name - on = school.official_name - # assert n == on + + # Toto platí před prvním spuštením skriptu, pak už ne (změníme place.name) + # assert place.name == school.official_name schools.append( { @@ -40,7 +59,8 @@ for school, place, parent_place in schools_q: } ) -schools_orig = copy.deepcopy(schools) +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) def sorted_by_length(schools): @@ -52,33 +72,32 @@ def sorted_by_length(schools): def summarize(schools, k=5): lens = [len(sc["names"][-1]) for sc in schools] avg_len = sum(lens) / len(schools) - print("Average length:", avg_len) - print("Maximum length:", max(lens)) + eprint("Average length:", avg_len) + eprint("Maximum length:", max(lens)) names_by_lens = sorted_by_length(schools) - print() - print(f"{k} longest:") + eprint() + eprint(f"{k} longest:") for sc in names_by_lens[::-1][:k]: - print(f'{sc["names"][-1]} (@{sc["city"]})') + eprint(f'{sc["names"][-1]} (@{sc["city"]})') random.shuffle(names_by_lens) - print() - print(f"{k} random:") + eprint() + eprint(f"{k} random:") for sc in names_by_lens[:k]: - print(f'Old: {sc["names"][0]}') - print(f'{sc["names"][-1]}') - print() + eprint(f'Old: {sc["names"][0]}') + eprint(f'{sc["names"][-1]}') + eprint() city_rules = [ - (r"(\w)-(\w)", r"\1 - \2"), + (r"(\w)-(\w)", r"\1 - \2"), # Mezery kolem pomlček jsou někdy nekonzistentní ("Praha", "v Praze"), ("v Praze 4", "v Praze 12"), - ("v Praze 4", "v Praze 12"), (r"v Praze [0-9]+", "v Praze"), ("v Praze", "Praha"), - None, # Dummy at the end + None, # Dummy ] school_kinds = [ @@ -119,14 +138,18 @@ def shorten_name(name): def partition(name, city): + """Rozdělí název školy na část před názvem města a část po názvu města""" + + # Zkouší drobné úpravy názvu města for rule in city_rules: - # Eat up rest of the word for cases like "Táborské" + # Pro slova jako "Táborské" chceme odstranit i zbytek slova, nejen "Tábor" pat = r"\b{}\w*\b".format(city) + if re.search(pat, name) is not None: parts = re.split(pat, name) if len(parts) != 2: - # Multiple occurrences of city - what to do? + # Název města se vyskytuje víckrát, není jasné, co dělat return None else: ok = True @@ -135,7 +158,8 @@ def partition(name, city): ok = False if not ok: - # Part of the school kind follows after city name + # Názvová část školy pokračuje i po názvu města (např. "Táborské gymnázium"), + # nelze automaticky vyřešit return None else: return parts @@ -143,19 +167,13 @@ def partition(name, city): if rule is not None: city = re.sub(rule[0], rule[1], city) - # Failed to find match + # Nenašli jsme název města return [name] -def shorten_in_city(city, schools): - for sc in schools: - name_p, place_p = sc["parts"] - sc["name"] = "|".join([name_p, city, place_p]) - - def remove_house_number(name): name, n = re.subn(r"(, ([^\W\d_]| |\.)+) [0-9/]+[a-z]?$", r"\1", name) - # True if changed, False if not + # True, pokud se název změnil return name, n > 0 @@ -177,7 +195,7 @@ def shorten_all(schools): sc["names"].append(remove_formalities(sc["names"][-1])) sc["parts"] = partition(sc["names"][-1], sc["city"]) - print("Total schools: {}".format(len(schools))) + eprint("Total schools: {}".format(len(schools))) n_split = 0 @@ -185,9 +203,11 @@ def shorten_all(schools): sc["names"].append(shorten_name(sc["names"][-1])) if sc["parts"] is not None: if len(sc["parts"]) == 1: - # City name not found in school name + # Název města nenalezen v názvu školy sc["names"].append(f"{sc['names'][-1]}, {sc['city']}") else: + # Když máme rozdělení, můžeme zkusit odstanit číslo popisné + # a případně i celý název ulice n_split += 1 assert len(sc["parts"]) == 2 @@ -206,26 +226,21 @@ def shorten_all(schools): sc["names"].append(f"{p_name} {sc['city']}") - print(f"Successfully split up {n_split} schools") + eprint(f"Successfully split up {n_split} schools") return schools - -schools = copy.deepcopy(schools_orig) -shortened = shorten_all(schools) - - def is_conflict(names1, names2): return any([(name in names1) for name in names2]) def remove_conflicts(shortened): - n_conflicts = 0 + """Vrátí se k delším variantám jmen, pokud se vyskytly konflikty""" again = True while again: shortened.sort(key=lambda sc: sc["names"][-1]) - print("----------------------------") + eprint("----------------------------") n_conflicts = 0 again = False @@ -244,15 +259,16 @@ def remove_conflicts(shortened): assert len(sc["names"]) > 1 sc["names"].pop() - print(f"Found {n_conflicts} conflicts") + eprint(f"Found {n_conflicts} conflicts") - # Hack - fix false positives, we always want to do these changes + # Hack - tato zkrácení vždy chceme aplikovat, předpokládáme, že nevzniknou konflikty for sc in shortened: sc["names"].append(remove_formalities(shorten_name(sc["names"][-1]))) - print("Done (possible unremovable conflicts)") + eprint("Done (possible unremovable conflicts)") +shortened = shorten_all(schools) remove_conflicts(shortened) summarize(shortened, k=10)