生成最短的正则表达式以匹配任意单词列表

时间:2011-09-15 14:48:22

标签: regex

我希望有人可能知道一个脚本,该脚本可以采用任意单词列表并生成可以完全匹配该列表的最短正则表达式(并且没有别的)。

例如,假设我的列表是

1231
1233
1234
1236
1238
1247
1256
1258
1259

然后输出应为:

12(3[13468]|47|5[589])

4 个答案:

答案 0 :(得分:4)

您可能最好保存整个列表,或者如果您想获得幻想,请创建Trie

1231
1234
1247

    1
    |
    2 
   / \
  3   4
 / \   \
1   4   7

现在当你接受一个字符串检查它是否到达叶子节点。确实如此,这是有效的。

如果你有可变长度的重叠字符串(例如:123和1234),你需要将某些节点标记为可能是终端。


如果您真的喜欢正则表达式的想法,也可以使用trie生成正则表达式:

  1. 从根到第一个分支的节点是固定的(例如:12

  2. 分支机构创建| :(例如:12(3|4)

  3. 叶子节点生成一个跟随父节点的字符类(或单个字符):(例如12(3[14]|47)

  4. 这可能不会生成最短的正则表达式,为此你可能会做一些额外的工作:

    1. 如果找到“紧凑”范围(例如[12345]变为[1-4]

    2. 为重复元素添加量词(例如:[1234][1234]变为[1234]{2}

    3. ???

    4. 我真的不认为生成正则表达式是值得的。

答案 1 :(得分:3)

此项目从给定的单词列表生成正则表达式:https://github.com/bwagner/wordhierarchy

它几乎与above JavaScript solution相同,但避免使用某些多余的括号。 它仅使用" |",非捕获组" (?:)"和选项" ?"。 当有一行单个字符时,还有改进的余地: 而不是例如(?:3|8|1|6|4)它可以生成[38164]

生成的正则表达式可以很容易地适应其他正则表达式方法。

样本用法:

java -jar dist/wordhierarchy.jar 1231 1233 1234 1236 1238 1247 1256 1258 1259
-> 12(?:5(?:6|9|8)|47|3(?:3|8|1|6|4))

答案 2 :(得分:2)

这是我提出的(JavaScript)。它将20,000个6位数的列表转换为60,000个字符的正则表达式。与天真(word1 | word2 | ...)构造相比,按字符数计算,这几乎是“压缩”的60%。

我将问题保持开放,因为仍有很大的改进空间,我希望可能有更好的工具。

var list = new listChar("");

function listChar(s, p) {
    this.char = s;
    this.depth = 0;
    this.parent = p;
    this.add = function(n) {
        if (!this.subList) {
            this.subList = {};
            this.increaseDepth();
        }
        if (!this.subList[n]) {
            this.subList[n] = new listChar(n, this);
        }
        return this.subList[n];
    }
    this.toString = function() {
        var ret = "";
        var subVals = [];
        if (this.depth >=1) {
            for (var i in this.subList) {
                subVals[subVals.length] = this.subList[i].toString();
            }
        }
        if (this.depth === 1 && subVals.length > 1) {
            ret = "[" + subVals.join("") + "]";
        } else if (this.depth === 1 && subVals.length === 1) {
            ret = subVals[0];
        } else if (this.depth > 1) {
            ret = "(" + subVals.join("|") + ")";
        }
        return this.char + ret;
    }
    this.increaseDepth = function() {
        this.depth++;
        if (this.parent) {
            this.parent.increaseDepth();
        }
    }
}

function wordList(input) {
    var listStep = list;
    while (input.length > 0) {
        var c = input.charAt(0);
        listStep = listStep.add(c);
        input = input.substring(1);
    }
}
words = [/* WORDS GO HERE*/];
for (var i = 0; i < words.length; i++) {
    wordList(words[i]);
}

document.write(list.toString());

使用

words = ["1231","1233","1234","1236","1238","1247","1256","1258","1259"];

这是输出:

(1(2(3[13468]|47|5[689])))

答案 3 :(得分:1)

这是一篇旧帖子,但是为了那些通过网络搜索找到它的人的好处,有一个Perl模块执行此操作,名为Regexp::Optimizer,此处:http://search.cpan.org/~dankogai/Regexp-Optimizer-0.23/lib/Regexp/Optimizer.pm

它采用正则表达式作为输入,它可以只包含用|分隔的输入字符串列表,并输出最佳正则表达式。

例如,这个Perl命令行:

perl -mRegexp::Optimizer -e "print Regexp::Optimizer->new->optimize(qr/1231|1233|1234|1236|1238|1247|1256|1258|1259/)"

生成此输出:

(?^:(?^:12(?:3[13468]|5[689]|47)))

(假设您已经安装了Regex::Optimizer),这与OP的预期非常匹配。

这是另一个例子:

perl -mRegexp::Optimizer -e "print Regexp::Optimizer->new->optimize(qr/314|324|334|3574|384/)"

输出:

(?^:(?^:3(?:[1238]|57)4))

为了进行比较,最佳基于trie的版本将输出3(14|24|34|574|84)。在上面的输出中,您还可以使用(?:搜索并替换(?^:(并删除多余的括号,以获取此信息:

3([1238]|57)4