Regex 如何从给定的字符串列表自动生成正则表达式?

Regex 如何从给定的字符串列表自动生成正则表达式?,regex,algorithm,Regex,Algorithm,您将获得两个字符串列表-A和B。查找与A中的所有字符串匹配且不与B中的所有字符串匹配的最短正则表达式。请注意,此正则表达式可以匹配/不匹配不在A和B中的其他字符串。为简单起见,我们可以假设字母表大小仅为2个字符-0和1。此外,仅允许以下操作员: *-0或更多 ? - 0或1 +-1个或多个 ()括号 为简单起见,不允许使用正则表达式not运算符。我不知道允许or运算符(|)是否会简化问题。当然,A和B没有共同的元素。以下是一些示例: A=[00,01,10] B=[11] answer = 1*

您将获得两个字符串列表-A和B。查找与A中的所有字符串匹配且不与B中的所有字符串匹配的最短正则表达式。请注意,此正则表达式可以匹配/不匹配不在A和B中的其他字符串。为简单起见,我们可以假设字母表大小仅为2个字符-0和1。此外,仅允许以下操作员:

*-0或更多
? - 0或1
+-1个或多个
()括号

为简单起见,不允许使用正则表达式not运算符。我不知道允许or运算符(|)是否会简化问题。当然,A和B没有共同的元素。以下是一些示例:

A=[00,01,10]
B=[11]
answer = 1*0+1*


如果这是一个家庭作业问题,它将像“一个家庭作业,在课堂上得a”类型。 我认为在这个问题的某个地方缺少“或”操作符

有一个明显的解决方案是A0 | A1 | A2 |……,但当试图找到最短的解决方案时,似乎更难解决


我建议使用递归来缩短正则表达式,但这不是一个理想的解决方案。

解决这个问题的一种方法是使用遗传算法。我碰巧有一个问题,所以我用下面的算法来解决你的问题:

  • 从所需的输入中获取不同的标记作为基因
  • 将正则表达式特殊值添加到基因中
  • 关于适应度算法
    • 确保生成的字符串是有效的正则表达式
    • 根据它匹配的所需内容的数量和 它匹配了多少不想要的东西
  • 直到找到一个成功的正则表达式
    • 从不同令牌的数量开始,并根据需要递增
    • 尝试生成一个长度为该长度的正则表达式,该正则表达式通过了适应度要求
这是我在C中的实现#

输出:

Generation  1 best: 10 (2)
Generation  2 best: 0+ (2)
Generation  5 best: 0* (2)
Generation  8 best: 00 (2)
Generation  9 best: 01 (2)
-- not solved with regex of length 2
Generation  1 best: 10* (2)
Generation  3 best: 00* (2)
Generation  4 best: 01+ (2)
Generation  6 best: 10+ (2)
Generation  9 best: 00? (2)
Generation 11 best: 00+ (2)
Generation 14 best: 0?1 (2)
Generation 21 best: 0*0 (2)
Generation 37 best: 1?0 (2)
Generation 43 best: 10? (2)
Generation 68 best: 01* (2)
Generation 78 best: 1*0 (2)
Generation 79 best: 0*1 (2)
Generation 84 best: 0?0 (2)
Generation 127 best: 01? (2)
Generation 142 best: 0+1 (2)
Generation 146 best: 0+0 (2)
Generation 171 best: 1+0 (2)
-- not solved with regex of length 3
Generation  1 best: 1*0+ (1)
Generation  2 best: 0+1* (1)
Generation 20 best: 1?0+ (1)
Generation 31 best: 1?0* (1)
-- not solved with regex of length 4
Generation  1 best: 1*00? (1)
Generation  2 best: 0*1?0 (1)
Generation  3 best: 1?0?0 (1)
Generation  4 best: 1?00? (1)
Generation  8 best: 1?00* (1)
Generation 12 best: 1*0?0 (1)
Generation 13 best: 1*00* (1)
Generation 41 best: 0*10* (1)
Generation 44 best: 1*0*0 (1)
-- not solved with regex of length 5
Generation  1 best: 0+(1)? (1)
Generation 36 best: 0+()1? (1)
Generation 39 best: 0+(1?) (1)
Generation 61 best: 1*0+1? (0)
solved with: 1*0+1?
Generation  1 best: 00 (2)
Generation  2 best: 01 (2)
Generation  7 best: 0* (2)
Generation 12 best: 0+ (2)
Generation 33 best: 1+ (2)
Generation 36 best: 1* (2)
Generation 53 best: 11 (2)
-- not solved with regex of length 2
Generation  1 best: 00* (2)
Generation  2 best: 0+0 (2)
Generation  7 best: 0+1 (2)
Generation 12 best: 00? (2)
Generation 15 best: 01* (2)
Generation 16 best: 0*0 (2)
Generation 19 best: 01+ (2)
Generation 30 best: 0?0 (2)
Generation 32 best: 0*1 (2)
Generation 42 best: 11* (2)
Generation 43 best: 1+1 (2)
Generation 44 best: 00+ (2)
Generation 87 best: 01? (2)
Generation 96 best: 0?1 (2)
Generation 125 best: 11? (2)
Generation 126 best: 1?1 (2)
Generation 135 best: 11+ (2)
Generation 149 best: 1*1 (2)
-- not solved with regex of length 3
Generation  1 best: 0*1* (0)
solved with: 0*1*
第二个样本:

public void Given_Sample_B()
{
    var target = new[] { "00", "01", "11" };
    var dontMatch = new[] { "10" };

    GenerateRegex(target, dontMatch);
}
输出:

Generation  1 best: 10 (2)
Generation  2 best: 0+ (2)
Generation  5 best: 0* (2)
Generation  8 best: 00 (2)
Generation  9 best: 01 (2)
-- not solved with regex of length 2
Generation  1 best: 10* (2)
Generation  3 best: 00* (2)
Generation  4 best: 01+ (2)
Generation  6 best: 10+ (2)
Generation  9 best: 00? (2)
Generation 11 best: 00+ (2)
Generation 14 best: 0?1 (2)
Generation 21 best: 0*0 (2)
Generation 37 best: 1?0 (2)
Generation 43 best: 10? (2)
Generation 68 best: 01* (2)
Generation 78 best: 1*0 (2)
Generation 79 best: 0*1 (2)
Generation 84 best: 0?0 (2)
Generation 127 best: 01? (2)
Generation 142 best: 0+1 (2)
Generation 146 best: 0+0 (2)
Generation 171 best: 1+0 (2)
-- not solved with regex of length 3
Generation  1 best: 1*0+ (1)
Generation  2 best: 0+1* (1)
Generation 20 best: 1?0+ (1)
Generation 31 best: 1?0* (1)
-- not solved with regex of length 4
Generation  1 best: 1*00? (1)
Generation  2 best: 0*1?0 (1)
Generation  3 best: 1?0?0 (1)
Generation  4 best: 1?00? (1)
Generation  8 best: 1?00* (1)
Generation 12 best: 1*0?0 (1)
Generation 13 best: 1*00* (1)
Generation 41 best: 0*10* (1)
Generation 44 best: 1*0*0 (1)
-- not solved with regex of length 5
Generation  1 best: 0+(1)? (1)
Generation 36 best: 0+()1? (1)
Generation 39 best: 0+(1?) (1)
Generation 61 best: 1*0+1? (0)
solved with: 1*0+1?
Generation  1 best: 00 (2)
Generation  2 best: 01 (2)
Generation  7 best: 0* (2)
Generation 12 best: 0+ (2)
Generation 33 best: 1+ (2)
Generation 36 best: 1* (2)
Generation 53 best: 11 (2)
-- not solved with regex of length 2
Generation  1 best: 00* (2)
Generation  2 best: 0+0 (2)
Generation  7 best: 0+1 (2)
Generation 12 best: 00? (2)
Generation 15 best: 01* (2)
Generation 16 best: 0*0 (2)
Generation 19 best: 01+ (2)
Generation 30 best: 0?0 (2)
Generation 32 best: 0*1 (2)
Generation 42 best: 11* (2)
Generation 43 best: 1+1 (2)
Generation 44 best: 00+ (2)
Generation 87 best: 01? (2)
Generation 96 best: 0?1 (2)
Generation 125 best: 11? (2)
Generation 126 best: 1?1 (2)
Generation 135 best: 11+ (2)
Generation 149 best: 1*1 (2)
-- not solved with regex of length 3
Generation  1 best: 0*1* (0)
solved with: 0*1*
“有疑问时,使用暴力。”

重新导入
def读入(赞成,反对,最大尺寸=7):
def正常(接收):
rx+='$'
返回(所有(重新匹配(接收、接收)以“是”为单位)
而不是任何(重新匹配(rx,s)中的s)
返回find(查找(gen_size(大小),是否正常)
适用于范围内的尺寸(最大尺寸+1))
def find(xs,ok=lambda x:x):
对于xs中的x:
如果确定(x):
返回x
def gen_尺寸(尺寸):
如果0==大小:
屈服“
如果0<大小:
对于发电机尺寸(尺寸1)的rx:
收益率rx+“0”
收益率rx+‘1’
如果rx和rx[-1]不在“*?+”中:
产量rx+“*”
产量rx+“?”
产量rx++
如果5<尺寸:
对于gen_尺寸(尺寸3)的rx:
收益率'(%s)*'%rx
产量'(%s)?'%rx
产量'(%s)+'%rx
这为第一个问题提供了一个不同但同样好的答案:
0*1?0*
。它着眼于1241个试验正则表达式来解决这两个测试用例(总计)


搜索有一个大小限制——因为这个问题的一般正则表达式版本是NP难的,因此任何用于搜索的程序都会在足够复杂的输入上遇到麻烦。我承认我没有真正考虑过这个简化的问题。我希望看到一些简洁而不那么明显的答案。

此项目根据给定的单词列表生成一个regexp:

但是,它仅使用“
|
”、非捕获组“
(?:)
”和选项“

示例用法:

java -jar dist/wordhierarchy.jar 00 01 10
-> 10|0(?:1|0)

java -jar dist/wordhierarchy.jar 00 01 11
-> 0(?:0|1)|11

java -jar dist/wordhierarchy.jar 000 001 010 011 100 101 110 111
-> 1(?:0(?:0|1)|1(?:0|1))|0(?:1(?:1|0)|0(?:1|0))

java -jar dist/wordhierarchy.jar 000 001 010     100 101 110 111
-> 1(?:0(?:0|1)|1(?:1|0))|0(?:10|0(?:1|0))

这听起来像是一个相当困难的问题,一个生成相当短的表示的算法可能并不难找到,但要证明它生成了短的表示可能是很棘手的。有趣的是,不允许任何替换。似乎有可能产生“病态”集合,而这些集合不能为它们生成正则表达式。比如说,
[0,00,0000,00000]
[000,0000000]
。你打算如何证明它是最短的呢?我已经大学毕业很久了,这不是家庭作业,但感谢你认为我可以把问题写清楚。是的,我认为这是可行的,但在任何合理的时间内都不行。如果我们能够解决这个问题而不必担心集合B(比如我们被告知B总是空的),或者我们被告知不仅B是空的,而且A只有2个元素,那么可以先对攻击进行一些简化。对于类似的问题和长谈:+1,这很好。你试过大一点的,长一点的吗?我想在
A
中有10个长度为~10的字符串,为了简单起见,将
B
设为空。我不知道那会有多快。当您不需要精确解时,GA往往工作得更好,否则它们通常效率很低。@IVlad不,我没有尝试更长的字符串。就@MK的观点而言,遗传算法可能不适用于复杂的输入集。遗传算法是一种聪明的方法,但不幸的是它无法推广(它试图尽可能多地约束)。也许再加上一个递归神经网络,它可以得到有趣的结果;肯定是值得研究的东西。但它对学习不起作用(((“0”,“00”,“0000”,“00000”),(“000”,“000000”))@royas,你期望的答案是什么?@royas,与“000”匹配,一个否定的答案。max_size@Darius Bacon没有答案,如何?正则表达式中的哪些零与字符串000中的哪些零匹配?等效且更容易分析的是0(0(000?)?对于varchar(123)varchar(…)@silentsudo,它失败了。请提供一个完整、可重复的示例。
import re

def learn(ayes, noes, max_size=7):
    def is_ok(rx):
        rx += '$'
        return (all(re.match(rx, s) for s in ayes)
                and not any(re.match(rx, s) for s in noes))
    return find(find(gen_sized(size), is_ok)
                for size in range(max_size + 1))

def find(xs, ok=lambda x: x):
    for x in xs:
        if ok(x):
            return x

def gen_sized(size):
    if 0 == size:
        yield ''
    if 0 < size:
        for rx in gen_sized(size-1):
            yield rx + '0'
            yield rx + '1'
            if rx and rx[-1] not in '*?+':
                yield rx + '*'
                yield rx + '?'
                yield rx + '+'
    if 5 < size:
        for rx in gen_sized(size-3):
            yield '(%s)*' % rx
            yield '(%s)?' % rx
            yield '(%s)+' % rx
java -jar dist/wordhierarchy.jar 00 01 10
-> 10|0(?:1|0)

java -jar dist/wordhierarchy.jar 00 01 11
-> 0(?:0|1)|11

java -jar dist/wordhierarchy.jar 000 001 010 011 100 101 110 111
-> 1(?:0(?:0|1)|1(?:0|1))|0(?:1(?:1|0)|0(?:1|0))

java -jar dist/wordhierarchy.jar 000 001 010     100 101 110 111
-> 1(?:0(?:0|1)|1(?:1|0))|0(?:10|0(?:1|0))