Алгоритмы нормализации в Unicode нужны для преобразования внутренней структуры текста, чтобы потом с ним было проще работать. Например, можно заменить несколько символов одним, убрать все диакритические знаки из текста и даже преобразовать похожие буквы в их аналоги.
Всего есть четыре таких алгоритма: NFD, NFC, NFKD, NFKC. Каждый в отдельности можно запускать на одной и той же строке много раз, и результат от этого никак не изменится. То есть они идемпотентны.
Работать с Unicode текстом будем при помощи страндартной Python библиотеки unicodedata.
1. NFD (Normalization Form Canonical Decomposition) или форма нормализации D.
Раскладывает составные символы на несколько простых в соответствии с таблицами декомпозиции. Если хотя бы один из получившихся символов тоже составной, раскладываем и его до тех пор, пока не получим последовательность простых символов. То есть, алгоритм работает рекурсивно.
Получившаяся разложенная последовательность сортируется в некотором порядке, пока не очень понял в каком именно.
for c in unicodedata.normalize("NFD", "ё"):
print("'%s': %s" % (c, unicodedata.name(c)))
>> 'е': CYRILLIC SMALL LETTER IE
>> '̈': COMBINING DIAERESIS
Так можно быстро подчистить всю диакритику в тексте, удаляя все ненужные группы символов из строки и оставляя только буквы.
2. NFC (Normalization Form Canonical Composition) или форма нормализации C.
Сначала выполняет NFD декомпозицию, а затем комбинирует полученные простые символы в составные. NFD декомпозиция тут нужна, чтобы разбить уже частично комбинированные символы на простые составляющие для последующей сортировки и обратной сборки.
for c in unicodedata.normalize("NFC", "ё"):
print("'%s': %s" % (c, unicodedata.name(c)))
>> 'ё': CYRILLIC SMALL LETTER IO
Так можно быстро «приклеить» всю диакритику к буквам и получить из двух символов CYRILLIC SMALL LETTER IE и COMBINING DIAERESIS один CYRILLIC SMALL LETTER IO.
3. NFKD (Normalization Form Compatibility Decomposition) или форма нормализации KD.
Алгоритм, который выполняет NFD декомпозицию и заменяет похожие символы совместимыми аналогами, например, дробь ’¼’ заменяется на строку символов «1/4».
s = '⑲ ⁹ ¼'
print(unicodedata.normalize("NFKD", s))
>> 1 19 9 1/4
for c in list(s):
print("'%s': %s - '%s'" % (c, unicodedata.name(c), unicodedata.normalize("NFKD", c)))
>> '⑲': CIRCLED NUMBER NINETEEN - '19'
>> ' ': SPACE - ' '
>> '⁹': SUPERSCRIPT NINE - '9'
>> ' ': SPACE - ' '
>> '¼': VULGAR FRACTION ONE QUARTER - '1⁄4'
После такой нормализации можно легко делать фильтрацию текста регэкспами, если его пытались усложнить и замаскировать от этого заменой похожих символов.
4. NFKC (Normalization Form Compatibility Composition) или форма нормализации KC.
Сначала выполняется совместимое разложение NFKD, а затем символы собираются вместе согласно NFC. Аналогично работе с NFC, можно быстро склеить всю диакритику с буквами, приведенными к некоторому базовому виду.
Проблемы NFKD и NFKC
При всем удобстве алгоритмов NFKD и NFKC, они не приводят некоторую часть визуально похожих символов к совместимым аналогам. Например, группу Negative Circled Number * из блока Enclosed Alphanumerics вполне можно привести к числам, но этого не происходит:
unicodedata.normalize("NFKC", "⓫ ⓯")
>> '⓫ ⓯'
А значит, если хочется сделать качественное приведение похожих символов, необходимое для многих задач, придется повозиться над своими таблицами замен.