Как реализовать соответствие строк в Unicode, складывая в python

У меня есть приложение, реализующее инкрементный поиск. У меня есть каталог строк юникода, который нужно сопоставить, и сопоставлять их с данной "ключевой" строкой; строка каталога является "хитом", если она содержит все символы в ключе, по порядку, и она лучше оценивается, если кластер ключей кластер в строке каталога.

Во всяком случае, это отлично работает и точно соответствует юникоду, так что "öst" будет соответствовать " Öst blocket" или "r öst" или "r ö d st ru".

В любом случае, теперь я хочу реализовать сворачивание, поскольку есть некоторые случаи, когда нецелесообразно различать символ каталога, такой как "á" или "é", и ключевой символ "a" или "e".

Например: "Ole" должен соответствовать "Olé"

Как лучше всего реализовать этот разворачивающий юникод в Python? Эффективность важна, так как мне приходится сопоставлять тысячи строк каталога с коротким заданным ключом.

Не нужно превращать его в ascii; Фактически, выходная строка алгоритма может быть unicode. Оставить персонажа лучше, чем лишить его.


Я не знаю, какой ответ принять, поскольку я использую немного обоих. Принимая декомпозицию NKFD и удаляя комбинации меток, вы почти закончите, я добавлю к ним некоторые пользовательские транслитерации. Вот модуль, как он выглядит сейчас: (Предупреждение, содержит символы unicode inline, так как гораздо лучше редактировать этот путь.)

# -*- encoding: UTF-8 -*-

import unicodedata
from unicodedata import normalize, category

def _folditems():
    _folding_table = {
        # general non-decomposing characters
        # FIXME: This is not complete
        u"ł" : u"l",
        u"œ" : u"oe",
        u"ð" : u"d",
        u"þ" : u"th",
        u"ß" : u"ss",
        # germano-scandinavic canonical transliterations
        u"ü" : u"ue",
        u"å" : u"aa",
        u"ä" : u"ae",
        u"æ" : u"ae",
        u"ö" : u"oe",
        u"ø" : u"oe",
    }

    for c, rep in _folding_table.iteritems():
        yield (ord(c.upper()), rep.title())
        yield (ord(c), rep)

folding_table = dict(_folditems())

def tofolded(ustr):
    u"""Fold @ustr

    Return a unicode str where composed characters are replaced by
    their base, and extended latin characters are replaced by
    similar basic latin characters.

    >>> tofolded(u"Wyłącz")
    u'Wylacz'
    >>> tofolded(u"naïveté")
    u'naivete'

    Characters from other scripts are not transliterated.

    >>> tofolded(u"Ἑλλάς") == u'Ελλας'
    True

    (These doctests pass, but should they fail, they fail hard)
    """
    srcstr = normalize("NFKD", ustr.translate(folding_table))
    return u"".join(c for c in srcstr if category(c) != 'Mn')

if __name__ == '__main__':
    import doctest
    doctest.testmod()

(И для фактического соответствия, если это кого-то интересует: я заранее создаю свернутые строки для всего моего каталога и помещаю свернутые версии в уже имеющееся свойство псевдонимов каталога.)

Ответы

Ответ 1

Вы можете использовать эту strip_accents функцию для удаления акцентов:

def strip_accents(s):
   return ''.join((c for c in unicodedata.normalize('NFD', unicode(s)) if unicodedata.category(c) != 'Mn'))

>>> strip_accents(u'Östblocket')
'Ostblocket'

Ответ 2

Для моего приложения я уже обратился к этому в другом комментарии: я хочу получить результат unicode и оставить необработанные символы нетронутыми.

В этом случае правильный способ сделать это - создать объект-коллактор UCA с его силой, установленной для сравнения только с первичной силой, что тем самым полностью игнорирует диакритические знаки.

Я покажу, как это сделать, используя Perl в этом ответе. Первый объект-коллактор находится в нужной степени, в то время как второй рассматривает акценты на разрыв связи.

Вы заметите, что никакие строки не пострадали при составлении этих сравнений: исходные данные не тронуты.

Ответ 4

Решение общего назначения (особенно для нормализации поиска и создания пули) - это модуль унифицированного кода:

http://pypi.python.org/pypi/Unidecode

Это порт модуля Text:: Unidecode для Perl. Он не завершен, но он переводит все латинские символы, которые я мог найти, транслитерирует кириллицу, китайский и т.д. На латынь и даже правильно обрабатывает символы полной ширины.

Вероятно, неплохо просто отбросить все символы, которые вы не хотите иметь в конечном выпуске, или заменить их наполнителем (например, "äßœ$" будет унифицирован до "assoe$", поэтому вам может понадобиться не-буквенно-цифровой). Для символов он транслитерируется, но не должен (скажем, § = > SS и = > EU), вам нужно очистить вход:

input_str = u'äßœ$'
input_str = u''.join([ch if ch.isalnum() else u'-' for ch in input_str])
input_str = str(unidecode(input_str)).lower()

Это заменит все неалфавитно-цифровые символы на замену фиктивной буквы, а затем транслитерирует строку и превратит ее в нижний регистр.