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 отображает "многосимвольные" символы на "базовый" символ, как это происходит с предварительно составленными символами.

Примечание. Приведенный выше список может быть неполным, но, по крайней мере, должен решить значительную часть проблемы "предварительно составленный символ или комбинированный символ".

Другие вопросы по тегам