查找字符串是否与模式匹配

时间:2013-05-07 06:39:31

标签: regex node.js pattern-matching

在我的应用程序的某一点上,我需要将一些字符串与模式匹配。假设一些示例字符串如下所示:

  1. 你好,约翰。
  2. 今天多么美好的一天!
  3. 今天可爱的日落,约翰,不是吗?
  4. 你今天会见琳达,约翰?
  5. 这些字符串中的大多数(不是全部)来自预定义的模式,如下所示:

    1. “你好,%s。”
    2. “今天多么美好的一天!”
    3. “今天可爱的日落,%s,不是吗?”
    4. “你今天会见到%s,%s?”
    5. 此模式库不断扩展(目前约为1,500),但需要手动维护。但是输入字符串(第一组)在很大程度上是不可预测的。虽然他们中的大多数都会匹配其中一种模式,但其中一些不会。

      所以,这是我的问题:给定一个字符串(来自第一组)作为输入,我需要知道它匹配的是哪个模式(已知的第二组)。如果没有匹配,那就需要告诉我。

      我猜测解决方案涉及从模式中构建正则表达式,并迭代检查哪一个匹配。但是,我不确定构建这些正则表达式的代码是什么样的。

      注意:我在这里给出的字符串仅用于说明目的。实际上,字符串不是人类生成的,而是计算机生成的人性友好字符串,如上所示,来自我无法控制的系统。由于它们不是手动输入的,因此我们不需要担心拼写错误和其他人为错误。只需要找到它匹配的模式。

      注意2:我可以将模式库修改为其他格式,如果这样可以更容易构建正则表达式。 printf样式为%s的当前结构并非一成不变。

6 个答案:

答案 0 :(得分:3)

我将此视为解析问题。这个想法是解析器函数接受一个字符串并确定它是否有效。

如果您可以在给定模式中find,则该字符串有效。这意味着您需要所有模式的索引。索引必须是全文索引。它也必须根据单词位置匹配。例如。如果在模式的第一个字中没有找到输入的第一个字,它应该短路。它应该处理模式中的any匹配,即%s

一种解决方案是将模式放在内存数据库(例如redis)中并对其执行全文索引。 (根据单词位置不匹配)但是你应该能够通过将输入分成单词和搜索来缩小到正确的模式。搜索速度非常快,因为您的内存数据库很小。另请注意,您正在寻找最接近的匹配。一个或多个单词不匹配。匹配次数最多的是您想要的模式。

更好的解决方案是以字典格式生成自己的索引。以下是您作为JavaScript对象提供的四种模式的示例索引。

{
    "Hi": { "there": {"%s": null}},
    "What: {"a": {"lovely": {"day": {"today": null}}}},
    "Lovely": {"sunset": {"today": {"%s": {"isnt": {"it": null}}}}},
    "Will": {"you": {"be": {"meeting": {"%s": {"today": {"%s": null}}}}}}
}

此索引根据单词postion递归递减。所以搜索第一个单词,如果找到则搜索第一个单词返回的对象,依此类推。给定级别的相同单词只有一个键。您还应该匹配any案例。这应该在内存中快速致盲。

答案 1 :(得分:1)

我的第一个想法是让regexp引擎在处理这个问题时会遇到麻烦。它们通常经过优化处理大量文本,所以它不应该是性能上的麻烦。这是蛮力,但表现似乎没问题。您可以将输入分成多个部分并让多个进程处理它们。这是我经过适度测试的解决方案(在Python中)。

import random
import string
import re

def create_random_sentence():
    nwords = random.randint(4, 10)
    sentence = []
    for i in range(nwords):
        sentence.append("".join(random.choice(string.lowercase) for x in range(random.randint(3,10))))
    ret =  " ".join(sentence)
    print ret
    return ret



patterns = [ r"Hi there, [a-zA-Z]+.",
             r"What a lovely day today!",
             r"Lovely sunset today, [a-zA-Z]+, isn't it?",
             r"Will you be meeting [a-zA-Z]+ today, [a-zA-Z]+\?"]

for i in range(95):
    patterns.append(create_random_sentence())


monster_pattern = "|".join("(%s)"%x for x in patterns)

print monster_pattern
print "--------------"

monster_regexp = re.compile(monster_pattern)

inputs = ["Hi there, John.",
          "What a lovely day today!",
          "Lovely sunset today, John, isn't it?",
          "Will you be meeting Linda today, John?",
          "Goobledigoock"]*2000

for i in inputs:
    ret = monster_regexp.search(i)
    if ret:
        print ".",
    else:
        print "x",

我创造了一百个模式。这是python regexp库的最大限制。其中4个是你的实际例子,其余的是随机句子,只是为了强调性能。

然后我将它们组合成一个包含100个组的正则表达式。 (group1)|(group2)|(group3)|...。我猜你必须清理那些在正则表达式中有意义的东西的输入(比如?等)。这是monster_regexp

对此测试一个字符串会在一次拍摄中对100个模式进行测试。有一些方法可以提取匹配的确切组。我测试10000个字符串,其中80%应该匹配,10%不匹配。如果有成功的话它会缩短cirtcuits,它会相对较快。失败将必须贯穿整个正则表达式,因此速度会变慢。您可以根据输入频率对事物进行排序,以获得更多性能。

我在我的机器上运行了这个,这是我的时间。

python /tmp/scratch.py 0.13s user 0.00s system 97% cpu 0.136 total

这不是太糟糕。

但是,要针对如此大的正则表达式运行模式并且失败将需要更长时间,因此我将输入更改为具有大量随机生成的字符串,这些字符串将无法匹配然后尝试。 10000个字符串都没有匹配monster_regexp,我得到了这个。

python /tmp/scratch.py 3.76s user 0.01s system 99% cpu 3.779 total

答案 2 :(得分:1)

与Noufal的解决方案类似,但返回匹配的模式或无。

import re

patterns = [
    "Hi there, %s.",
    "What a lovely day today!",
    "Lovely sunset today, %s, isn't it",
    "Will you be meeting %s today, %s?"
]

def make_re_pattern(pattern):
    # characters like . ? etc. have special meaning in regular expressions.
    # Escape the string to avoid interpretting them as differently.
    # The re.escape function escapes even %, so replacing that with XXX to avoid that. 
    p = re.escape(pattern.replace("%s", "XXX"))
    return p.replace("XXX", "\w+")

# Join all the pattens into a single regular expression.
# Each pattern is enclosed in () to remember the match. 
# This will help us to find the matched pattern.
rx = re.compile("|".join("(" + make_re_pattern(p) + ")" for p in patterns))

def match(s):
    """Given an input strings, returns the matched pattern or None."""
    m = rx.match(s)
    if m:
        # Find the index of the matching group. 
        index = (i for i, group in enumerate(m.groups()) if group is not None).next()
        return patterns[index]

# Testing with couple of patterns
print match("Hi there, John.")
print match("Will you be meeting Linda today, John?")

答案 3 :(得分:0)

Python解决方案。 JS应该是类似的。

>>> re2.compile('^ABC(.*)E$').search('ABCDE') == None
False
>>> re2.compile('^ABC(.*)E$').search('ABCDDDDDDE') == None
False
>>> re2.compile('^ABC(.*)E$').search('ABX') == None
True
>>> 

诀窍是使用^和$绑定您的模式并使其成为“模板”。使用(。*)或(。+)或您要“搜索”的任何内容。

你的主要瓶颈是imho,它将遍历这些模式的列表。正则表达式搜索的计算成本很高。

如果你想要“做任何模式匹配”的结果,建立一个大规模的基于OR的正则表达式,并让你的正则表达式引擎为你处理'OR'ing。

此外,如果您只有前缀模式,请查看TRIE数据结构。

答案 4 :(得分:0)

这可能是sscanf的工作,js中有一个实现:http://phpjs.org/functions/sscanf/;被复制的函数是:http://php.net/manual/en/function.sscanf.php

你应该能够在不改变准备好的琴弦的情况下使用它,但我对表演有疑问。

答案 5 :(得分:0)

这个问题对我来说并不清楚。你想采用模式并从中构建正则表达式吗? 大多数正则表达式引擎都有一个“引用字符串”选项。 (\ Q \ E)。所以你可以拿出字符串并制作它 ^ \ QHi那里,\ E(?:。*)\ Q. \ E $ 这些将是与变量之外的字符串完全匹配的正则表达式。

如果您想使用单个正则表达式来匹配单个模式,您可以将它们放在分组模式中以找出哪个匹配,但这不会给您每个匹配,只是第一个。

如果您使用正确的解析器(我使用过PEG.js),它可能更易于维护。如果你认为你可能会陷入正则表达式地狱,那么这是另一种选择