2009-09-11 53 views
9

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

无论如何,这工作正常,并完全匹配的Unicode,让 “OST” 将匹配 “OST blocket” 或 “R OST” 或 “R ö d ST恩”。

无论如何,现在我想要实现折叠,因为有些情况下区分诸如“á”或“é”这样的目录字符与关键字符“a”或“e”是无用的。

例如:“奥莱报”应与“奥莱报”

如何最好地实现在Python这个unicode折叠匹配?效率非常重要,因为我必须将数千个目录字符串与短的给定密钥进行匹配。

它不必把它变成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() 

(而且,对于实际的匹配,如果感兴趣的人:你建造折我所有的目录字符串时事先,并把折叠版本到已经可用的目录对象的别名属性)

+0

这真的很酷,并且可能对自动填写人名非常有用,因为大多数人在搜索名字时不会介绍重音。我正在研究如何在Java中做类似的事情。这似乎处理一些情况:http://java.sun.com/javase/6/docs/api/java/text/Collat​​or.html。 – 2009-09-12 19:57:49

+0

是的。请注意,如果您想让它们变得重音剥离,您可能希望在上面的特殊表格中忽略'ü,å,ä,ö'。那些双重扩展是我从我的POV中想要的(更正确地降低我的语言);其他语言中存在不幸的例外情况(例如西班牙语)。 – u0b34a0f6ae 2009-09-24 20:00:37

回答

5

可以使用thisstrip_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' 
+0

这会在字符串中留下未处理的字符,而不是剥离它们,这很好。 'Dźwięk - > Dzwiek','Wyłącz - >Wyłacz';那么我可以在 – u0b34a0f6ae 2009-09-11 11:56:56

+0

之上添加特殊情况,我接受这个答案,因为过滤归一化几乎是一路走来的。检查我使用的更新问题。 – u0b34a0f6ae 2009-09-12 19:26:42

+1

但是,请注意,您要过滤NF ** K ** D;这是一个没有严格等价的分解,例如下标字符被转换为纯数字。 – u0b34a0f6ae 2009-09-12 19:50:40

1

通用溶液(特别是搜索正常化并产生蛞蝓)是unidecode模块:

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

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

这可能是一个好主意,只是剥夺你不希望在最终输出或用填料全部更换字符(例如"äßœ$"将unidecoded到"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() 

这与虚拟更换更换所有非字母数字字符和然后音译字符串并将其变成小写字母。约

+0

谢谢你的提示。我的应用程序是多语言的,我不确定是否要过分严格的ascii别名。我目前的解决方案有不同的但很酷的功能,如日本脚本'ヘ'将匹配'ペ',就像'a'将匹配'ä';变音剥离更普遍,虽然我还没有建立,如果这是有用的或不(我只有西方用户的反馈)。 – u0b34a0f6ae 2010-03-02 16:27:19

+0

@ u0b34a0f6ae - 在什么样的情况下你想'ペ'(pe)匹配'ヘ'(他)? – simon 2013-11-12 00:49:41

1

这个怎么样:

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

从这里拍摄(西班牙)http://python.org.ar/pyar/Recetario/NormalizarCaracteresUnicode

+0

对于我的应用程序,我已经在一个不同的评论中解决了这个问题:我想要一个* unicode *结果并*保持未处理的字符不变*。 – u0b34a0f6ae 2010-05-09 11:37:56

4

对于我的申请,我已经在不同的注释解决这样的:我想有一个的Unicode结果和不处理未处理的字符

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

我演示了如何使用Perl在this answer中执行此操作。第一个比赛对象是你需要的力量,而第二个比赛对象则是考虑打破平局的口音。

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