我相信,如果您有大量模式,这是一个解决方案应该可以很好地工作。仅仅 10k 可能有点过头了,实现它意味着相对大量的工作,但你可能仍然感兴趣。
基本思想是创建一个倒排索引,将模式的子字符串映射到模式 ID。首先,每个模式都有一个 ID:
1: what is *
2: where is *
3: do * need to
etc.
然后我们创建一个倒排索引。在最简单的情况下,我们将模式拆分为标记并将每个标记映射到它出现的模式 ID 列表。我们可以灵活地定义标记,但一种方法是假设每个空格分隔的单词是一个令牌。所以这里是索引:
what -> 1
is -> 1,2
where -> 2
do -> 3
need -> 3
to -> 3
然后,当您从用户那里获得输入字符串时,您将其拆分为标记并在索引中查找它们。您可以组合从索引中获得的所有模式 ID。例子:
INPUT: what is something?
TOKENS:
what -> 1
is -> 1,2
something -> n/a
您检索每个标记的模式 ID 并将它们放入一个临时数据结构中,该数据结构计算每个 ID 的频率,例如哈希(例如 a std::unordered_map<id_type,std::size_t>
)。
然后,您按频率对其进行排序,以发现规则 1 被找到了两次,而规则 2 被找到了一次。
然后,您按照频率的顺序将找到的规则应用于输入文本。在这里,您使用正则表达式库或类似的东西来生成匹配项。最频繁的规则与输入文本有最多的共同标记,因此它很可能匹配得很好。
该方法的总体优势是您不需要将所有规则应用于输入,而只需将那些与输入至少具有一个共同标记的规则应用,甚至在那些您按照每个规则有多少标记的顺序执行的规则中与输入共享,一旦找到匹配规则,您可能会中断匹配过程的其余部分(或不 - 取决于您是否想要每种情况下的所有匹配规则,或者只需要一个非常好的匹配)。
改进以上执行基于令牌的规则预选。相反,您可以像这样连接所有规则:
what is *||where is *||do * need to||...
然后你构造这个连接字符串的后缀数组。
然后,给定一个输入字符串,将其与后缀数组进行匹配,以识别所有子字符串匹配项,包括小于一个标记或跨越多个标记的匹配项。在上面的例子中,我假设通配符*
和$
包含在后缀数组中,当然输入字符串的任何部分都不会匹配它们。您可以很好地将它们从后缀数组中排除或用虚拟字符替换它们。
一旦确定了匹配项,就可以按长度对它们进行排序。您还必须将连接字符串中的匹配位置映射到规则 ID。这很容易通过维护规则相对于连接字符串的起始位置数组来实现;还有一些基于索引位向量的高度优化的方法(如有必要,我可以详细说明)。
获得匹配规则 ID 后,您可以执行与倒排索引案例相同的操作:使用标准正则表达式匹配(或类似方法)应用匹配规则。
同样,这种方法相对复杂,并且仅在您拥有大量规则时才有意义,并且基于标记(或基于子字符串)的查找可能会显着减少候选规则的数量。从您给出的示例规则中,我假设在这种情况下是后者,但是如果您正在处理的规则数量(大约 10k)证明这种方法是合理的,我不确定。如果规则的总数在 100ks 或数百万个,这可能更有意义。