优雅的方式匹配两个通配字符串

时间:2010-10-14 18:57:37

标签: python regex string string-matching

我正在从两个不同的来源录制一些文字。他们每个人都可以在不同的地方犯错,他们不会识别一封信/一组信件。如果他们不认识某事,那就换成了?例如,如果单词为Roflcopter,则一个来源可能会返回Ro?copter,而另一个来源可能会返回Roflcop?er。我想要一个函数来返回两个匹配是否相等,允许多个? s。例如:

match("Ro?copter", "Roflcop?er") --> True
match("Ro?copter", "Roflcopter") --> True
match("Roflcopter", "Roflcop?er") --> True
match("Ro?co?er", "Roflcop?er") --> True

到目前为止,我可以使用正则表达式将一个OCR与一个完美的OCR匹配:

>>> def match(tn1, tn2):
    tn1re = tn1.replace("?", ".{0,4}")
    tn2re = tn2.replace("?", ".{0,4}")

    return bool(re.match(tn1re, tn2) or re.match(tn2re, tn1))

>>> match("Roflcopter", "Roflcop?er")
True
>>> match("R??lcopter", "Roflcopter")
True

但是当它们都在不同的地方时,这不起作用:

>>> match("R??lcopter", "Roflcop?er")
False

4 个答案:

答案 0 :(得分:2)

嗯,只要一个?对应一个字符,然后我可以建议一个高性能和紧凑的方法。

def match(str1, str2):
    if len(str1) != len(str2): return False
    for index, ch1 in enumerate(str1):
        ch2 = str2[index]
        if ch1 == '?' or ch2 == '?': continue
        if ch1 != ch2: return False
    return True

>>> ================================ RESTART ================================
>>> 
>>> match("Roflcopter", "Roflcop?er")
True
>>> match("R??lcopter", "Roflcopter")
True
>>> 
>>> match("R??lcopter", "Roflcop?er")
True
>>> 

编辑:B部分),脑屁现在免费。

def sets_match(set1, set2):
    return any(match(str1, str2) for str1 in set1 for str2 in set2)

>>> ================================ RESTART ================================
>>> 
>>> s1 = set(['a?', 'fg'])
>>> s2 = set(['?x'])
>>> sets_match(s1, s2) # a? = x?
True
>>> 

答案 1 :(得分:2)

感谢Hamish Grubijan的这个想法。每个?在我的ocr'd名称中可以是0到3个字母。我所做的是将每个字符串扩展为可能的扩展列表:

>>> list(expQuestions("?flcopt?"))
['flcopt', 'flcopt@', 'flcopt@@', 'flcopt@@@', '@flcopt', '@flcopt@', '@flcopt@@', '@flcopt@@@', '@@flcopt', '@@flcopt@', '@@flcopt@@', '@@flcopt@@@', '@@@flcopt', '@@@flcopt@', '@@@flcopt@@', '@@@flcopt@@@']

然后我展开两个并使用他的匹配函数,我称之为matchats

def matchOCR(l, r):
    for expl in expQuestions(l):
        for expr in expQuestions(r):
            if matchats(expl, expr):
                return True
    return False

按需运作:

>>> matchOCR("Ro?co?er", "?flcopt?")
True
>>> matchOCR("Ro?co?er", "?flcopt?z")
False
>>> matchOCR("Ro?co?er", "?flc?pt?")
True
>>> matchOCR("Ro?co?e?", "?flc?pt?")
True

<小时/> 匹配功能:

def matchats(l, r):
    """Match two strings with @ representing exactly 1 char"""
    if len(l) != len(r): return False
    for i, c1 in enumerate(l):
        c2 = r[i]
        if c1 == "@" or c2 == "@": continue
        if c1 != c2: return False
    return True

和扩展函数,其中cartesian_product就是这样:

def expQuestions(s):
    """For OCR w/ a questionmark in them, expand questions with
    @s for all possibilities"""
    numqs = s.count("?")

    blah = list(s)
    for expqs in cartesian_product([(0,1,2,3)]*numqs):
        newblah = blah[:]
        qi = 0
        for i,c in enumerate(newblah):
            if newblah[i] == '?':
                newblah[i] = '@'*expqs[qi]
                qi += 1
        yield "".join(newblah)

答案 2 :(得分:1)

使用Levenshtein distance可能很有用。它将给出字符串彼此相似程度的值。如果它们的长度不同,也可以使用。链接页面有一些伪造的代码可以让你入门。

你最终会得到这样的东西:

>>> match("Roflcopter", "Roflcop?er")
1
>>> match("R??lcopter", "Roflcopter")
2
>>> match("R?lcopter", "Roflcop?er")
3

所以你可以有一个最大阈值,低于你可能认为的最高阈值。

答案 3 :(得分:1)

这可能不是最Pythonic的选项,但如果允许?匹配任意数量的字符,那么以下回溯搜索可以解决问题:

def match(a,b):
    def matcher(i,j):
        if i == len(a) and j == len(b):
            return True
        elif i < len(a) and a[i] == '?' \
          or j < len(b) and b[j] == '?':
            return i < len(a) and matcher(i+1,j) \
                or j < len(b) and matcher(i,j+1)
        elif i == len(a) or j == len(b):
            return False
        else:
            return a[i] == b[j] and matcher(i+1,j+1)

    return matcher(0,0)

这可以适应更严格的匹配。此外,为了节省堆栈空间,最终的情况(i+1,j+1)可能会转换为非递归解决方案。

编辑:针对以下反应做出更多澄清。这是对简化正则表达式/ NFA的简单匹配算法的改编(参见Kernighan对 Beautiful Code 的贡献,O'Reilly 2007或Jurafsky&amp; Martin,语音和语言处理 ,Prentice Hall 2009)。

工作原理:matcher函数递归遍历两个字符串/模式,从(0,0)开始。当它到达两个字符串(len(a),len(b))的末尾时它会成功;当遇到两个不相等的字符或一个字符串的结尾时,它会失败,而另一个字符串中仍然存在要匹配的字符。

matcher在任一字符串中遇到变量(?)时(比如a),它可以做两件事:跳过变量(匹配零个字符),或跳过在b中的下一个字符上,但指向a中的变量,允许它匹配更多字符。