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:
vow_indices = [i for i, char_w in enumerate(word_segment) if self._is_vow(char_w)]
# Если в слове нет гласных, то перенос невозможен
if not vow_indices:
word_len = len(word_segment)
min_part = self.min_chars_per_part
# --- Вложенная функция для оценки качества точки переноса ---
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
word_segment_len = len(word_segment)
# Ищем ближайшую гласную до или после start_idx
for i in vow_indices:
if i >= start_idx - self.min_chars_per_part and i + self.min_chars_per_part < word_segment_len:
# Проверяем, что после гласной есть минимум символов "хвоста"
ind = i + 1
# 1. Не отделяем "хвостов" с начала или конца (это некрасиво)
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 # Не нашли подходящую позицию
# 3. Сортируем кандидатов: сначала по убыванию ОЦЕНКИ, потом по возрастанию УДАЛЕННОСТИ от центра.
# Это гарантирует, что перенос "н-н" (score=10) будет выбран раньше, чем "е-н" (score=5),
# даже если "е-н" чуть ближе к центру.
best_candidate = sorted(candidates, key=lambda c: (-c['score'], c['distance']))[0]
return best_candidate['index'] # Не нашли подходящую позицию
# Рекурсивное деление слова
def split_word_ru(word_to_split: str) -> str:

View File

@@ -7,28 +7,28 @@ from etpgrf import Hyphenator
# Используем \u00AD - это Unicode-представление мягкого переноса (&shy;)
RUSSIAN_HYPHENATION_CASES = [
("дом", "дом"), # Сочень короткое (короче 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дной"), # Неправильный перенос
]