Postgresql levenshtein 和预组合字符与组合字符

Postgresql levenshtein and precomposed character vs. combined character

我有包含两个相似字符的字符串。两者都显示为带有 ogonek 的小 'a's:

±

±

(注意:根据渲染器的不同,它们有时呈现相似,有时略有不同)

但是,它们是不同的:

第一个字符的特征:

在 PostgreSQL 中:

select ascii('ą');
ascii 
-------
261

十六进制的 UTF-8 编码是:\xC4\x85

所以是预组字 (https://en.wikipedia.org/wiki/Precomposed_character)

第2个字符的特征:

在 PostgreSQL 中:

select ascii('ą');
ascii 
-------
97

(与字符 'a' 相同)

这强烈表明渲染的字符是由两个字符组合而成的。确实是:

十六进制的 UTF-8 编码是:\x61\xCC\xA8

所以是

的组合

一个\x61\

和一个组合字符 (https://en.wikipedia.org/wiki/Combining_character),单独的ogonek:

̨ \xCC\xA8

我想使用 PostgreSQL 的 levenshtein 函数来确定单词的相似性,因此我想将两个字符视为相同的(因为这当然是写作者的意图具有第一个或第二个字符的独特实体的名称)。

我假设我可以使用 unaccent 来始终摆脱 ogonek,但这在第二种情况下不起作用:

第一个字符:预期结果:

select levenshtein('ą', 'x');
levenshtein 
-------------
       1

第一个字符:预期结果:

select levenshtein(unaccent('ą'), 'x');
levenshtein 
-------------
       1

第二个字符:预期结果:

select levenshtein('ą', 'x');
levenshtein 
-------------
       2

第二个字符:意外结果:

select levenshtein(unaccent('ą'), 'x');
levenshtein 
-------------
       2

所以,当我将两个字符与 levenshteinunaccent 进行比较时,结果是 1:

select levenshtein(unaccent('ą'), unaccent('ą'));
levenshtein 
-------------
       1

而不是 0。

第2种情况如何"get rid of the ogonek"?

(如何)使用String的UTF-8编码可以得到这样的结果?

Edit:正如@s-man 所建议的,将组合字符添加到 unaccent.rules 将解决这个特定问题。但是要用 precomposed character vs. combined character 来解决 unaccent 的问题,我必须明确地add/modify 每个缺失/"misconfigured" 组合字符 to/in 配置。

您必须按照 https://postgresql.org/docs/current/unaccent.html

中的说明更改您的配置并在配置文件中手动添加缺少的字符

Note: This solution is based on @S-Man's suggestion to explicitly add missing characters to the unaccent.rules file.

Note: A prerequisite of this answer is that the relevant precomposed characters (https://en.wikipedia.org/wiki/Precomposed_character) are already mapped in the unaccent.rules file. If not, they have to be added also.

有多个字符组成的字符:

目标是在包含的“基本”字符上映射一个“多字符”字符。

(假设对应的预组字符映射到“基本”字符,原始unaccent.rules文件中就是这种情况)

unaccent 检查“多字符”字符中的每个字符进行替换,因此无需考虑基本字符和变音符号的每个组合。

相反,变音符号必须映射到 [nothing]。这可以通过将 unaccent.rules 文件 (https://postgresql.org/docs/current/unaccent.html) 的第二列留空来实现。

这是从 https://en.wikipedia.org/wiki/Diacritic 获得的拉丁字母变音符号列表: ´ ˝ ` ̏ ^ ˇ ˘ ̑ ¸ ¨ · ̡ ̢ ̉ ̛ ͅ ˉ ˛ ͂ ˚ ˳ ῾ ᾿

再加上缺少的问题的 ogonek: ̨

现在(当然是在 PostgreSQL 重新启动之后),unaccent 将“多字符”字符映射到“基本”字符,就像它对预组合字符所做的那样。

注意:上面的列表可能并不全面,但至少应该解决了“预组合字符与组合字符”问题的很大一部分。

删除重音符号会使编辑距离为 0,但也会使 ąa 之间的距离为 0,这听起来并不理想。

更好的解决方案是 normalise Unicode 字符串,即在比较它们之前将组合字符序列 E'a\u0328' 转换为预组合字符 E'\u0105'

不幸的是,Postgres 似乎没有 built-in Unicode 规范化功能,但您可以通过 PL/Perl or 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

这也解决了 中的问题,其中组合字符对 Levenshtein 距离有贡献:

test=# select levenshtein(unicode_normalize(E'a\u0328'), 'x');
 levenshtein
-------------
           1