package ru.pdguard.detect; import java.util.Locale; import java.util.Set; /** * Словарь населённых пунктов России — проверка того, что значение, пойманное правилом {@code * ADDRESS_CITY}, действительно похоже на существующий город, село, посёлок или другой населённый * пункт, а не на произвольное слово с заглавной буквы после якоря. * *

Не только официальные города (~1100 по классификатору): перепись добавляет сёла, деревни, * хутора, станицы — «рп. Ильинское», «с. Кукуево» из ТЗ находятся ровно за счёт неё. Какой * конкретно тип населённого пункта стоит перед названием, определяет якорь самого правила в {@link * RuleRegistry}, а не этот словарь — он только подтверждает, что название реальное. * *

Сравнение по началу слова, а не точным совпадением: падежные окончания («в Москве», «из * Казани») тем самым покрываются без отдельного разбора морфологии, как и у известных людей в * {@link NameDictionary}. */ public final class ToponymDictionary { private static final Set SETTLEMENT_STEMS = ResourceLoader.set("/names/settlements.txt").stream() .map(Declension::withoutInflectedEnding) .collect(java.util.stream.Collectors.toUnmodifiableSet()); private ToponymDictionary() {} /** * Похоже ли значение на название населённого пункта из словаря в любом падеже. * *

Названия на согласную склоняются добавлением окончания («Тамбов» → «Тамбове»), поэтому * начало слова из словаря — уже достаточный признак. Названия на гласную меняют последнюю букву * («Москва» → «Москве»), для них сравнение идёт по основе без неё — так же, как с личными именами * в {@link NameDictionary}. * *

Проверяются префиксы значения по множеству, а не каждая из ~80 000 основ по значению: * перебор списка на каждое совпадение правила был бы на порядки дороже, чем нужно — префиксов у * слова не больше, чем в нём букв. */ public static boolean isKnownSettlement(String value) { String lower = value.strip().toLowerCase(Locale.ROOT); for (int length = lower.length(); length > 0; length--) { if (SETTLEMENT_STEMS.contains(lower.substring(0, length))) { return true; } } return false; } }