mod: изменен алгоритм переноса в русских словах (императивнный на декларативный) с весами и приоритетами
This commit is contained in:
@@ -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:
|
||||
|
@@ -7,28 +7,28 @@ from etpgrf import Hyphenator
|
||||
# Используем \u00AD - это Unicode-представление мягкого переноса (­)
|
||||
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дной"), # Неправильный перенос
|
||||
]
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user