mod: изменен алгоритм переноса в русских словах (императивнный на декларативный) с весами и приоритетами

This commit is contained in:
2025-07-24 21:02:40 +03:00
parent 086adc1f7b
commit d716d394bb
2 changed files with 80 additions and 73 deletions

View File

@@ -143,60 +143,67 @@ class Hyphenator:
# Поиск допустимой позиции для переноса около заданного индекса # Поиск допустимой позиции для переноса около заданного индекса
def find_hyphen_point_ru(word_segment: str, start_idx: int) -> int: def find_hyphen_point_ru(word_segment: str, start_idx: int) -> int:
vow_indices = [i for i, char_w in enumerate(word_segment) if self._is_vow(char_w)] word_len = len(word_segment)
# Если в слове нет гласных, то перенос невозможен min_part = self.min_chars_per_part
if not vow_indices:
# --- Вложенная функция для оценки качества точки переноса ---
def get_split_score(i: int) -> int:
"""
Вычисляет "оценку" для точки переноса `i`. Чем выше оценка, тем качественнее перенос.
-1 означает, что перенос в этой точке запрещен.
"""
# --- Сначала идут ЗАПРЕТЫ (жесткие "нельзя") ---
# Если правило нарушено, сразу дисквалифицируем точку.
if self._is_sign(word_segment[i]) or self._is_j_sound(word_segment[i]):
return -1 # ЗАПРЕТ 1: Новая строка не может начинаться с Ь, Ъ или Й.
if self._is_j_sound(word_segment[i - 1]) and self._is_vow(word_segment[i]):
return -1 # ЗАПРЕТ 2: Нельзя отрывать Й от следующей за ней гласной.
# --- Теперь идут РАЗРЕШЕНИЯ с разными приоритетами ---
# РАЗРЕШЕНИЕ 1: Перенос между сдвоенными согласными.
if self._is_cons(word_segment[i - 1]) and word_segment[i - 1] == word_segment[i]:
return 10
# РАЗРЕШЕНИЕ 2: Перенос после "слога" с Ь/Ъ, если дальше идет СОГЛАСНАЯ.
# Пример: "строитель-ство", но НЕ "компь-ютер".
# По-хорошему нужно проверять, что перед Ь/Ъ нет йотированной гласной
if self._is_sign(word_segment[i - 1]) and self._is_cons(word_segment[i]):
return 9
# РАЗРЕШЕНИЕ 3: Перенос после "слога" если предыдущий Й (очень качественный перенос).
if self._is_j_sound(word_segment[i - 1]):
return 7
# РАЗРЕШЕНИЕ 4: Перенос между тремя согласными (C-CС), чуть лучше, чем после гласной.
if self._is_cons(word_segment[i]) and self._is_cons(word_segment[i-1]) and self._is_cons(word_segment[i+1]):
return 6
# # РАЗРЕШЕНИЕ 5 (?): Перенос между согласной и согласной (C-C).
# if self._is_cons(word_segment[i - 1]) and self._is_cons(word_segment[i]):
# return 5
# РАЗРЕШЕНИЕ 6 (Основное правило): Перенос после гласной.
if self._is_vow(word_segment[i - 1]):
return 5
# Если ни одно правило не подошло, точка не подходит для переноса.
return 0
# 1. Собираем всех кандидатов и их оценки
candidates = []
possible_indices = range(min_part, word_len - min_part + 1)
for i in possible_indices:
score = get_split_score(i)
if score > 0:
# Добавляем только подходящих кандидатов
distance_from_center = abs(i - start_idx)
candidates.append({'score': score, 'distance': distance_from_center, 'index': i})
# 2. Если подходящих кандидатов нет, сдаемся
if not candidates:
return -1 return -1
word_segment_len = len(word_segment)
# Ищем ближайшую гласную до или после start_idx # 3. Сортируем кандидатов: сначала по убыванию ОЦЕНКИ, потом по возрастанию УДАЛЕННОСТИ от центра.
for i in vow_indices: # Это гарантирует, что перенос "н-н" (score=10) будет выбран раньше, чем "е-н" (score=5),
if i >= start_idx - self.min_chars_per_part and i + self.min_chars_per_part < word_segment_len: # даже если "е-н" чуть ближе к центру.
# Проверяем, что после гласной есть минимум символов "хвоста" best_candidate = sorted(candidates, key=lambda c: (-c['score'], c['distance']))[0]
ind = i + 1
# 1. Не отделяем "хвостов" с начала или конца (это некрасиво) return best_candidate['index'] # Не нашли подходящую позицию
if ind <= self.min_chars_per_part or ind >= word_segment_len - self.min_chars_per_part:
continue
# 2. Сдвигаем перенос за мягкий/твердый знак, если он сразу за согласной (ГОСТ 7.62-2008)
if self._is_sign(word_segment[ind]):
# 2.1 Текущая буква мягкий/твердый знак. Ставим перенос за ней (индекс ind+1).
return ind + 1
if (self._is_cons(word_segment[ind]) and
i+1 < word_segment_len and self._is_sign(word_segment[ind+1])):
# 2.2 Текущая буква согласная, а следующая мягкий/твердый знак. Ставим перенос за ней
return ind+2
# 3. Проверка на `Й` (полугласная). Не бывает слов, когда сразу не ней идет гласная,
# или перед ней идет согласная. Сдвигает перенос за полугласную букву если она идет после
# гласной.
if self._is_j_sound(word_segment[ind+1]):
# 3.1 Текущая буква `й`. Ставим за ней перенос (индекс ind+1).
return ind+1
if (self._is_vow(word_segment[ind]) and
i+1 < word_segment_len and self._is_j_sound(word_segment[ind+1])):
# 3.2 Текущая буква гласная, а следующая `й`. Ставим перенос за `й` (индекс ind+2).
# Ставим перенос за `й` (индекс ind+2).
return ind+2
# 4. Проверка на сдвоенная-согласная (C-C).
if (self._is_cons(word_segment[ind]) and
i+1 < word_segment_len and word_segment[ind] == word_segment[ind+1]):
print("сдвоенная согласная")
# 4.1 Текущая буква согласная и следующая така же (сдвоенная согласная). Ставим перенос
# за ней (индекс ind+1).
return ind + 1
if (self._is_cons(word_segment[ind]) and
i+1 < word_segment_len and self._is_cons(word_segment[ind+1])):
# 4.2 НЕ ОБЯЗАТЕЛЬНОЕ ПРАВИЛО: Текущая буква согласная, а следующая тоже согласная.
# Ставим перенос за ней (индекс ind+1).
return ind+1
# 5. Проверка на гласная-гласная (V-V).
if (self._is_vow(word_segment[ind]) and
i+1 < word_segment_len and self._is_vow(word_segment[ind+1])):
# 5.1 Текущая буква гласная, а следующая гласная. Перенос не делаем. Возможно,
# надо дальше искать до ближайшей согласной, но это усложнит алгоритм.
continue
# 6. TODO (опционально): Проверка на суффикс и приставку (не разбивать). Нужен словарь.
# 7. TODO (опционально): Проверка на короткий корень (не разбивать). Нужен очень большой словарь.
return ind
return -1 # Не нашли подходящую позицию
# Рекурсивное деление слова # Рекурсивное деление слова
def split_word_ru(word_to_split: str) -> str: def split_word_ru(word_to_split: str) -> str:

View File

@@ -7,28 +7,28 @@ from etpgrf import Hyphenator
# Используем \u00AD - это Unicode-представление мягкого переноса (&shy;) # Используем \u00AD - это Unicode-представление мягкого переноса (&shy;)
RUSSIAN_HYPHENATION_CASES = [ RUSSIAN_HYPHENATION_CASES = [
("дом", "дом"), # Сочень короткое (короче max_unhyphenated_len) не должно меняться ("дом", "дом"), # Сочень короткое (короче max_unhyphenated_len) не должно меняться
("проверка", "проверка"), # Короткое слово не должно меняться ("проверка", "про\u00ADверка"),
("тестирование", "тести\u00ADрование"), ("тестирование", "тести\u00ADрова\u00ADние"),
("благотворительностью", "благотво\u00ADритель\u00ADностью"), # Слово с переносом на мягкий знак ("благотворительностью", "бла\u00ADготво\u00ADритель\u00ADностью"), # Слово с переносом на мягкий знак
("гиперподъездной", "гипер\u00ADподъ\u00ADездной"), # Слово с переносом на твердый знак
("фотоаппаратура", "фотоап\u00ADпара\u00ADтура"), # проверка слова со сдвоенной согласной ("фотоаппаратура", "фотоап\u00ADпара\u00ADтура"), # проверка слова со сдвоенной согласной
("программирование", "програм\u00ADмиро\u00ADвание"), # слова со сдвоенной согласной ("программирование", "про\u00ADграм\u00ADмиро\u00ADвание"), # слова со сдвоенной согласной
("сверхзвуковой", "сверхзву\u00ADковой"), ("сверхзвуковой", "сверх\u00ADзву\u00ADковой"),
("автомобиль", "авто\u00ADмобиль"), ("автомобиль", "авто\u00ADмобиль"),
("интернационализация", "интерна\u00ADциона\u00ADлизация"), ("интернационализация", "инте\u00ADрнаци\u00ADонали\u00ADзация"),
("суперкомпьютер", "супер\u00ADком\u00ADпьютер"), ("электронный", "элек\u00ADтрон\u00ADный"),
("электронный", "электрон\u00ADный"), ("информационный", "инфо\u00ADрма\u00ADцион\u00ADный"),
("информационный", "информа\u00ADционный"), ("автоматизация", "автома\u00ADтиза\u00ADция"),
("автоматизация", "авто\u00ADмати\u00ADзация"), ("многоклеточный", "мно\u00ADгокле\u00ADточный"),
("многоклеточный", "многок\u00ADлеточ\u00ADный"), ("многофункциональный", "мно\u00ADгофун\u00ADкцио\u00ADналь\u00ADный"),
("многофункциональный", "многофун\u00ADкцио\u00ADнальный"), ("непрерывность", "непре\u00ADрывно\u00ADсть"),
("непрерывность", "непре\u00ADрывность"), ("сверхпроводимость", "сверх\u00ADпрово\u00ADдимо\u00ADсть"),
("сверхпроводимость", "сверхпро\u00ADводи\u00ADмость"), ("многообразие", "мно\u00ADгоо\u00ADбра\u00ADзие"),
("многообразие", "много\u00ADобразие"), ("противоречивость", "про\u00ADтиво\u00ADречи\u00ADвость"),
("противоречивость", "противо\u00ADречи\u00ADвость"), ("непревзойденный", "непре\u00ADвзой\u00ADден\u00ADный"),
("сверхчувствительный", "сверхчув\u00ADстви\u00ADтельный"), # Будет неправильный перенос, (словарь "корней") ("многослойный", "мно\u00ADгослой\u00ADный"),
("непревзойденный", "непрев\u00ADзойден\u00ADный"), # Будет неправильный перенос ("суперкомпьютер", "супе\u00ADрко\u00ADмпью\u00ADтер"), # Неправильный перенос (нужен словарь "приставок/корней/суффиксов")
("многослойный", "многос\u00ADлойный"), # Будет неправильный перенос, ("сверхчувствительный", "свер\u00ADхчув\u00ADстви\u00ADтель\u00ADный"), # Неправильный перенос
("гиперподъездной", "гипе\u00ADрпо\u00ADдъез\u00ADдной"), # Неправильный перенос
] ]