Postgresql Левенштейн и заранее составленный персонаж против комбинированного персонажа
У меня есть строки, содержащие два похожих символа. Оба выглядят как маленькие буквы с огонеком:
Ą
Ą
(Примечание: в зависимости от средства визуализации они иногда отображаются одинаково, иногда немного по-другому)
Однако они разные:
Характеристики 1-го персонажа:
В PostgreSQL:
select ascii('ą');
ascii
-------
261
Кодировка UTF-8 в шестнадцатеричном формате: \xC4\x85
так что это заранее составленный символ ( https://en.wikipedia.org/wiki/Precomposed_character)
Характеристики 2-го персонажа:
В PostgreSQL:
select ascii('ą');
ascii
-------
97
(так же, как символ "а")
Это сильно указывает на то, что визуализированный символ состоит из двух символов. И это действительно так:
Кодировка UTF-8 в шестнадцатеричном формате: \x61\xCC\xA8
Так что это сочетание
\x61\
и объединяющий символ ( https://en.wikipedia.org/wiki/Combining_character), отдельный огонек:
̨ \xCC\xA8
Я хочу использовать функцию Левенштейна в PostgreSQL, чтобы определить сходство слов, и поэтому я хочу, чтобы оба символа рассматривались как одинаковые (поскольку это, разумеется, предназначено для людей, которые пишут имя отличительного объекта с 1-м или 2-м символом),
Я предполагал, что могу использовать unaccent, чтобы всегда избавляться от огонек, но это не работает во 2-м случае:
1-й персонаж: ожидаемый результат:
select levenshtein('ą', 'x');
levenshtein
-------------
1
1-й персонаж: ожидаемый результат:
select levenshtein(unaccent('ą'), 'x');
levenshtein
-------------
1
2-й символ: ожидаемый результат:
select levenshtein('ą', 'x');
levenshtein
-------------
2
2-й персонаж: неожиданный результат:
select levenshtein(unaccent('ą'), 'x');
levenshtein
-------------
2
Итак, когда я сравниваю оба символа с левенштейном и без акцента, результат равен 1:
select levenshtein(unaccent('ą'), unaccent('ą'));
levenshtein
-------------
1
вместо 0.
Как я могу "избавиться от огонек" во 2-м случае?
(Как) я могу использовать коды строк UTF-8, чтобы получить достигнутый результат?
Редактировать: как предложил @ s-man, добавив объединяющий символ в unaccent.rules
решит эту конкретную проблему. Но чтобы вообще решить проблему предварительно составленного символа и объединенного символа с unaccent, мне пришлось бы явно добавить / изменить каждый отсутствующий /"неправильно настроенный" объединенный символ в / в конфигурации.
3 ответа
Удаление акцентов даст вам расстояние Левенштейна 0, но это также даст вам расстояние 0 между ą
а также a
, что не звучит идеально.
Лучшим решением было бы нормализовать строки Unicode, то есть преобразовать последовательность символов объединения E'a\u0328'
в заранее составленный персонаж E'\u0105'
прежде чем сравнивать их.
К сожалению, в Postgres, похоже, нет встроенной функции нормализации Unicode, но вы легко можете получить к ней доступ через расширения языка PL/Perl или PL/Python.
Например:
create extension plpythonu;
create or replace function unicode_normalize(str text) returns text as $$
import unicodedata
return unicodedata.normalize('NFC', str.decode('UTF-8'))
$$ language plpythonu;
А потом:
test=# select levenshtein(unicode_normalize(E'a\u0328'), unicode_normalize(E'\u0105'));
levenshtein
-------------
0
Это также решает проблему в вашем предыдущем вопросе, где комбинирующий персонаж способствовал расстоянию Левенштейна:
test=# select levenshtein(unicode_normalize(E'a\u0328'), 'x');
levenshtein
-------------
1
Вы должны изменить конфигурацию и добавить недостающие символы вручную в файле конфигурации, как описано в https://postgresql.org/docs/current/unaccent.html
Примечание. Это решение основано на предложении @S-Man явно добавить отсутствующие символы в
unaccent.rules
файл.Примечание. Предварительным условием этого ответа является то, что соответствующие предварительно составленные символы ( https://en.wikipedia.org/wiki/Precomposed_character) уже сопоставлены в
unaccent.rules
файл. Если нет, они должны быть добавлены также.
Есть символы, которые состоят из нескольких символов:
- "базовый" символ (например, гласные, такие как a, согласные, такие как l)
- объединяющий символ ( https://en.wikipedia.org/wiki/Combining_character), обычно такой диакритический знак, как острый ( ´) или точка ( ·)
Цель состоит в том, чтобы отобразить "многосимвольный" символ на содержащий "базовый" символ.
(при условии, что соответствующие предварительно составленные символы сопоставлены с "базовым" символом, что имеет место в оригинале unaccent.rules
файл)
unaccent проверяет каждый символ в "многосимвольном" символе для замены, поэтому нет необходимости рассматривать каждую комбинацию основного символа и диакритического знака.
Вместо этого диакритические знаки должны быть отображены на [ничего]. Этого можно достичь, оставив второй столбец в unaccent.rules
файл ( https://postgresql.org/docs/current/unaccent.html) пустой.
Это список диакритических знаков для латинского алфавита, полученный по https://en.wikipedia.org/wiki/Diacritic: ´ ˝ `̏ ˆ ˇ ˘ ̑ ¸ ¨ ̡ ̡ ̢ ̉ ͅ ͅ ͅ ͅ ͅ ͂ ˳ ˳
Добавьте к этому огонек вопроса, который отсутствует: ̨
Теперь (после перезапуска PostgreSQL, конечно), unaccent отображает "многосимвольные" символы на "базовый" символ, как это происходит с предварительно составленными символами.
Примечание. Приведенный выше список может быть неполным, но, по крайней мере, должен решить значительную часть проблемы "предварительно составленный символ или комбинированный символ".