如何通过在python中折叠来实现Unicode字符串匹配

时间:2009-09-11 11:15:48

标签: python unicode

我有一个实现增量搜索的应用程序。我有一个匹配的unicode字符串目录,并将它们与给定的“key”字符串匹配;如果目录字符串按顺序包含键中的所有字符,则它是“命中”字符串,如果目录字符串中的关键字符集群,则它的排名会更好。

无论如何,这样做很好并且完全匹配unicode,因此“öst”将匹配“Öst blocket”或“r öst”或“r ö d st zh“。

无论如何,现在我想实现折叠,因为在某些情况下,区分诸如“á”或“é”之类的目录字符和关键字符“a”或“e”是没有用的。 / p>

例如:“Ole”应匹配“Olé”

如何在Python中最好地实现这个unicode-folding matcher?效率非常重要,因为我必须将数千个目录字符串与短的给定键匹配。

它不必将其变成ascii;实际上,算法的输出字符串可以是unicode。留下一个角色比剥离它更好。


我不知道接受哪个答案,因为我使用了两者兼而有之。采用NKFD分解并删除组合标记几乎可以完成,我只添加了一些自定义音译。这是现在看起来的模块:(警告,包含内联的unicode字符,因为编辑这种方式更好。)

# -*- 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()

(并且,对于实际匹配,如果任何人感兴趣:我事先为我的所有目录构建折叠字符串,并将折叠版本放入已经可用的目录对象别名属性中。)

5 个答案:

答案 0 :(得分:6)

您可以使用this 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'

答案 1 :(得分:4)

  

对于我的应用程序,我已经在另一个注释中解决了这个问题:我想要一个 unicode 结果,并且保持未处理的字符不受影响。

在这种情况下,正确的方法是创建一个UCA collat​​or对象,其强度设置为仅在主要强度下进行比较,从而完全忽略变音符号。

我在this answer中展示了如何使用Perl执行此操作。第一个整理器对象具有您需要的强度,而第二个整体对象则考虑用于打破平局的重音。

您会注意到在进行这些比较时没有任何字符串受到损害:原始数据未受影响。

答案 2 :(得分:1)

看看这个:ftp://alan.smcvt.edu/hefferon/unicode2ascii.py

可能不完整,但可能会让你开始。

答案 3 :(得分:1)

通用解决方案(特别是搜索规范化和生成slu)是unidecode模块:

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

它是Perl的Text :: Unidecode模块的一个端口。它不完整,但它翻译了我能找到的所有拉丁文衍生的字符,将西里尔文,中文等音译成拉丁文,甚至可以正确处理全角字符。

简单地删除最终输出中不想要的所有字符或用填充符替换它们可能是一个好主意(例如"äßœ$"将不加编码为"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()

这将使用虚拟替换替换所有非字母数字字符,然后将该字符串音译并将其转换为小写。

答案 4 :(得分:1)

这个怎么样:

normalize('NFKD', unicode_string).encode('ASCII', 'ignore').lower()

取自这里(西班牙语)http://python.org.ar/pyar/Recetario/NormalizarCaracteresUnicode