【问题标题】:Fuzzy regular expressions模糊正则表达式
【发布时间】:2011-05-08 12:53:12
【问题描述】:

我正在寻找一种使用正则表达式进行模糊匹配的方法。 我想使用 Perl,但如果有人可以推荐任何方法来做这将是有帮助的。

例如,我想匹配“New York”字样上的一个字符串,前面有一个 2 位数字。困难在于文本来自PDF的OCR,所以我想做一个模糊匹配。我想匹配:

12 New York
24 Hew York
33 New Yobk

和其他“接近”匹配(在 Levenshtein 距离的意义上),但不是:

aa New York
11 Detroit

显然,我需要指定匹配的允许距离(“模糊度”)。

据我了解,我不能使用String::Approx Perl 模块来执行此操作,因为我需要在匹配项中包含正则表达式(以匹配前面的数字)。

另外,我应该注意,这是我真正想要匹配的一个非常简化的示例,所以我不是在寻找蛮力方法。

编辑添加:

好的,我的第一个例子太简单了。我的意思不是让人们沉迷于前面的数字——对这个不好的例子感到抱歉。这是一个更好的例子。考虑这个字符串:

ASSIGNOR, BY MESHS ASSIGN1IBNTS, TO ALUSCHALME&S MANOTAC/rURINGCOMPANY, A COBPOBATlOH OF DELAY/ABE.

这实际上是说:

ASSIGNOR, BY MESNE ASSIGNMENTS, TO ALLIS-CHALMERS MANUFACTURING COMPANY, A CORPORATION OF DELAWARE

我需要做的是提取短语“ALUSCHALME&S MANOTAC/rURINGCOMPANY”和“DELAY/ABE”。 (我意识到这可能看起来很疯狂。但我是一个乐观主义者。)一般来说,模式看起来像这样:

/Assignor(, by mesne assignments,)? to (company name), a corporation of (state)/i

匹配模糊的地方。

【问题讨论】:

  • 你还有更多的例子吗?这种情况可以使用 String::Approx — 只需将字符串拆分为数字部分和“纽约”部分。
  • 问题可能是识别纽约部分需要模糊匹配...
  • 非简化的实际案例是否涉及一个或多个固定字符串如“New York”?这个字符串可能有多长?您预计使用的允许距离范围是多少?您的示例仅显示更改的字符;你也需要处理额外/缺失的字符吗?
  • 嘿,这很简单:使用agreptgries.de/agrep 如果您可以劫持并使用源代码,请检查其许可证。
  • Udi Manber 的agrep 程序!一直使用它。 Udi 和他的妻子 Rachel 是我在大学里的教授。但我认为它没有被移植到 Perl。 Jarkko 在中世纪的某个时候有某种近似的 grep 模块,但在我看来,它根本不如 Udi 的方法好。检查 CPAN。

标签: regex perl fuzzy-comparison


【解决方案1】:

如果您有一个模式,您想针对文本集合找到最佳匹配,您可以尝试q-gram distance 。它很容易实现和适应特殊需求。

您的第二个描述实际上在这里很有帮助,因为 patterntexts 应该相当长。 q-gram 距离不适用于像“York”这样的词,但如果你的典型模式是一个完整的地址,那应该没问题。

试试这样:

  • 将您的文本和模式转换为简化的字符集,例如仅大写、剥离、字词化(单词之间有一个空格)所有被“#”替换的符号或其他东西。
  • 选择一个 q-gram 长度,以便使用。尝试 3 或 2。我们称之为q=3
  • 然后,为每个文本构建一个 qgram-profile
  • 将每个文本分成q-words,即。 NEW_YORK 变为 [NEW, EW_, W_Y, _YO, ORK],将其与每个文本一起存储。
  • 如果您搜索您的模式,那么您对您的模式执行相同的操作,
  • 遍历您的 text-qgram-database 和
    • 计算每个 pattern/text-pair 有多少个 qgram 是相同的。
    • 每次点击都会将分数提高 1。
  • 得分最高的文本是您的最佳点击率

如果你这样做了,你可以通过以下方式调整这个算法:

  • 在所有文本(以及搜索前的模式)前面加上q-1 特殊字符,因此即使是简短的单词也会得到不错的配置文件。例如,New York 变为 ^^NEW YORK$$
  • 您甚至可以将所有辅音替换为“x”,将元音替换为“o”等等。以这种方式玩几个字符类,甚至通过将字符组替换为另一个来创建超级符号,即CK变成K,或 SCH 变为 $
  • 当通过 qgram-hit 提高分数时,您可以通过其他因素调整 1 的值,例如 文本与模式的长度差异
  • 存储 2 克和 3 克两者,计数时,称重不同。

请注意,此处描述的基本形式的此算法在搜索期间没有良好的运行时间,即O(|T|*|P|)|T||P| 是您的文本的总长度和模式)。这是因为我描述了循环遍历所有文本,然后遍历 pattern。因此,这仅适用于中等大小的 texts-base。如果您花点心思,您可以在 q-gram 上创建一个高级 索引结构(可能使用哈希表),因此这对于大型 文本-bases 也可能是实用的.

【讨论】:

    【解决方案2】:

    正则表达式有特定的规则,它们不是为做你想做的事而构建的。两次通过它会容易得多。使用正则表达式去除数字,然后使用模块来关闭匹配。

    类似这样(假设您的输入是文件中的行)

    while( my $line = <$fh> ) {
        chomp $line;
    
        # do we have digits?
        if( $line =~ /^\d+/ ) {
             # removes spaces and digits from the beginning of the line
             $line =~ s/^[\d\s]*//g;
    
             # use your module to determine if you have a match in the remaining text.
             if( module_match ) {
                 # do something
             }
             else {
                 #no match
             }
        }
        else {
            # no match
        }
    }
    

    【讨论】:

      【解决方案3】:

      您可以尝试使用 Web 1T 5-gram Version 1 之类的方法和条件似然最大化方法。

      如果我没记错的话,Beautiful Data 的第 14 章专门介绍了这个数据集以及如何使用它来发现拼写错误等。

      【讨论】:

        【解决方案4】:

        您是否考虑过在 CPAN 上使用 Jarkko’s String::Approx 模块?它有 agrep 算法,但比 Udi 的慢很多。

        【讨论】:

          【解决方案5】:

          您是否考虑过两阶段测试,使用正则表达式强制要求[0-9]{2,2} (.*),然后捕获剩余文本并对其进行模糊匹配?试着把这个问题想象成一个正则表达式和一个模糊字符串的交集。

          【讨论】:

            【解决方案6】:

            把问题分成两部分:

            1. 匹配两位数。
            2. 模糊匹配残基与“纽约”。

            在示例中,您知道“纽约”由 2 个单词组成;您也许可以利用它更轻松地消除“底特律”(但不一定是“旧金山”)等替代方案。

            你甚至可以使用“String::Approx”,尽管它提到:

            ... CPAN 中的 Text::Levenshtein 和 Text::LevenshteinXS 模块。另请参阅 Text::WagnerFischer 和 Text::PhraseDistance。

            (我的 Perl 无法通过 CPAN 找到 Text::PhraseDistance - 其他的可用并且安装正常。)

            【讨论】:

            • 这些都是非常有用的参考资料,谢谢!顺便说一句,StackOverflow 如何解析@somebody 引用以获取通知?它只是做/\@(\w+)//\@(\S+)//\@([\w\s]+)\s*\pP/ 还是什么?换句话说,它是如何知道在查找用户名时停止扫描的地方?二维码可以扫描吗?
            • @tchrist:我不确定解析是在哪里完成的——可能不在客户端代码中(所以对我们不可见)。它使用名称的前三个字母,并将其与之前的 cmets 等相匹配。我不缩写以尽量减少找错人的风险。我认为如果您查看 SO 博客(一年或更长时间),您会发现关于它如何工作的讨论。
            • 谢谢,但我不认为这些允许正则表达式。我想我可以尝试以某种方式将距离与正则表达式结合使用,但坦率地说,我没有看到可以处理上面第二个示例的方法。
            • @itzy:是的,您的要求比您建议的问题的第一版要复杂得多。我假设扫描的字符串是一个真实的例子(或者足够接近以至于我无法区分)。您只有“ASSIGNOR”、“BY”、“TO”、“A”、“OF”等词来暂停分析——我也不相信这些词不会被错误识别。很多将取决于单词的可能值列表。 OF 后跟一个州名 - 在示例中。但是,如果一家公司是“爱达荷州的艾伦”(当然是特拉华州的一家公司),那么您又会遇到问题。
            【解决方案7】:

            您可以使用 Text::Levenshtein 缩小您的候选人范围,以通过与限制的比较来获得编辑距离和 grepping。

            但另一个想法是,您可以采用正确的形式并创建一个哈希键,从未命中的指向正确的形式,以便这些也可能成为候选者。

            对于正则表达式,您可能必须使用实验性的 code 部分,可能是这样的:

            m/ (?i: [new] | \p{Alpha} (?{ $misses++ }) ){2,4}
               \s+
              (?i: [york] | \p{Alpha} (?{ $misses++ }) ){3,5}
             /x
            

            虽然在这种情况下,您可能必须为每个正确的值设置一个正则表达式。您可能需要一些标志来指示您何时错过了目标。

            【讨论】:

            • 在 Perl 中,'grep' 具有特定含义(与 Unix 'grep' 松散相关),在这种情况下似乎不太可能有帮助。您可能需要重新表述您的答案。
            【解决方案8】:

            经验法则:当您必须前往 Stack Overflow 并询问“我如何在单个正则表达式中执行 X?”时你应该考虑用不止一个正则表达式来做 X。

            根据您的编辑,我会这样做:

            while(<>) {
              chomp;
              if(/assignor, by (\w+) (\w+), to (\w+), a (\w+) of (\w+)/i) {
                # now use String::Approx to check that $1, $2, $3, $4, and $5 match
              } else {
                warn "Errors!\n";
              }
            }
            

            我不会在这里给你一切。我没有使", by (\w+) (\w+)" 位可选来简化正则表达式,以便您了解它的要点。为此,您可能需要使用命名捕获和(?:) 非捕获组。我不想深入研究所有这些,只是想帮助您了解我将如何处理。

            请记住:如果您必须问“我如何在一个正则表达式中完成所有操作?”您应该停止尝试在单个正则表达式中完成所有操作。

            【讨论】:

            • 另外,您可能想检查“assignor”的拼写,但如果您得到其余部分,那应该很容易添加。此外,您可能希望将正则表达式中的所有空格更改为 \s+(或者可能是 `+`),以防有人不小心输入了两个空格。
            • 谢谢。我实际上并不是在寻找可以在一行中完成的事情。我写这样的模式是为了了解它通常会是什么样子,但我会采取任何方法。我想听从您的建议,但基本问题是任何地方都可能存在拼写错误——不能保证“购买”或“购买”是正确的。我想要一个匹配,允许在任何地方出现一些错误(模糊),但不要太多。我认为这就是让这件事变得如此困难的原因。我希望有人知道一些非常适合此类问题的软件包。
            【解决方案9】:

            虽然您指定了 perl,但 R 中内置了一个有用的算法,可以实现 Levenshtein 编辑距离。

            agrep()
            

            此命令还允许使用任何正则表达式或模式进行匹配。我建议你看看它。 http://stat.ethz.ch/R-manual/R-devel/library/base/html/agrep.html

            【讨论】:

              【解决方案10】:

              Python 正则表达式模块提供了一种在正则表达式中进行模糊匹配的方法:

              https://pypi.org/project/regex/(寻找近似“模糊”匹配)

              The fuzziness of a regex item is specified between “{” and “}” after the item.
              
              Examples:
              
              foo match “foo” exactly
              (?:foo){i} match “foo”, permitting insertions
              (?:foo){d} match “foo”, permitting deletions
              (?:foo){s} match “foo”, permitting substitutions
              (?:foo){i,s} match “foo”, permitting insertions and substitutions
              (?:foo){e} match “foo”, permitting errors
              If a certain type of error is specified, then any type not specified will not be permitted.
              
              In the following examples I’ll omit the item and write only the fuzziness:
              
              {d<=3} permit at most 3 deletions, but no other types
              {i<=1,s<=2} permit at most 1 insertion and at most 2 substitutions, but no deletions
              {1<=e<=3} permit at least 1 and at most 3 errors
              {i<=2,d<=2,e<=3} permit at most 2 insertions, at most 2 deletions, at most 3 errors in total, but no substitutions
              

              所以你可以写,例如:

              import regex, pprint
              
              m = regex.compile( r'(?:Assignor(, by mesne assignments,)? to (company name), a corporation of (state)){e}', regex.IGNORECASE ).match('ASSIGNOR, BY MESHS ASSIGN1IBNTS, TO ALUSCHALME&S MANOTAC/rURINGCOMPANY, A COBPOBATlOH OF DELAY/ABE.')
              
              pprint.pprint(m)
              pprint.pprint(m.groups())
              

              这不会立即起作用,结果将是:

              <regex.Match object; span=(0, 71), match='ASSIGNOR, BY MESHS ASSIGN1IBNTS, TO ALUSCHALME&S MANOTAC/rURINGCOMPANY,', fuzzy_counts=(45, 0, 0)>
              (', BY MESHS ASSIGN1IBNTS', ' ALUSCHALME&', 'PANY,')
              

              但是再进行一些调整(例如,您可以为每个捕获组指定最大错误数),您应该能够达到您的目标。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-06-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-05-29
                相关资源
                最近更新 更多