【问题标题】:Generate all matches from a subset of regex从正则表达式的子集生成所有匹配项
【发布时间】:2013-01-25 17:49:47
【问题描述】:

我需要定义一堆向量序列,它们都是一系列L,D,R,U 用于左、下、右、上或x 用于中断。有可选部分和非此即彼的部分。我一直在使用自己发明的系统来记录它,但我想记录下来,以供其他可能不是程序员的人阅读。

我现在想使用正则表达式的一个子集(我不打算使用任何通配符或无限重复)来定义向量序列和一个脚本来生成所有可能的匹配字符串...

/LDR/ produces ['LDR']
/LDU?R/ produces ['LDR','LDUR']
/R(LD|DR)U/ produces ['RLDU','RDRU']
/DxR[DL]U?RDRU?/ produces ['DxRDRDR','DxRDRDRU','DxRDURDR','DxRDURDRU','DxRLRDR','DxRLRDRU','DxRLURDR','DxRLURDRU']

我是否可以使用现有的库来生成所有匹配项

编辑

我意识到我只需要 or 语句,因为可选的东西可以由 thing or nothing 指定,可能是 a 或 b,两个可选的都可以是 (a|b|) .我可以使用另一种语言来定义我想要做什么吗?

【问题讨论】:

  • 你可以为你自己的正则表达式写一个解析器,然后遍历所有的可能性。但是 Dukeling 提供的链接有一个现有的库来进行生成。
  • @Dukeling:他有正则表达式,他想生成所有匹配它的字符串。
  • @billy ...你知道,我在下面更新了我的答案——我自己开发的“?”解析器现在似乎产生了比赛......有趣的挑战。

标签: regex


【解决方案1】:

通过将@Dukeling提供的链接中的java代码翻译成javascript,我想我已经解决了我的问题......

var Node = function(str){
    this.bracket = false;
    this.children = [];
    this.s = str;
    this.next = null;
    this.addChild = function(child){
        this.children.push(child);
    }
}

var printTree = function(root,prefix){
  prefix = prefix.replace(/\./g, "");
  for(i in root.children){
    var child = root.children[i]
    printTree(child, prefix + root.s);
  }
  if(root.children.length < 1){
    console.log(prefix + root.s);
  }
}

var Stack = function(){
    this.arr = []
    this.push = function(item){
        this.arr.push(item)
    }
    this.pop = function(){
        return this.arr.pop()
    }
    this.peek = function(){
        return this.arr[this.arr.length-1]
    }
}

var createTree = function(s){

    // this line was causing errors for `a(((b|c)d)e)f` because the `(((` was only
    // replacing the forst two brackets.
    // var s = s.replace(/(\(|\||\))(\(|\||\))/g, "$1.$2");
    // this line fixes it
    var s = s.replace(/[(|)]+/g, function(x){ return x.split('').join('.') });

    var str = s.split('');
    var stack = new Stack();
    var root = new Node("");
    stack.push(root); // start node
    var justFinishedBrackets = false;
    for(i in str){
        var c = str[i]
        if(c == '('){
            stack.peek().next = new Node("Y"); // node after brackets
            stack.peek().bracket = true; // node before brackets
        } else if (c == '|' || c == ')'){
            var last = stack.peek(); // for (ab|cd)e, remember b / d so we can add child e to it
            while (!stack.peek().bracket){ // while not node before brackets
                stack.pop();
            }
            last.addChild(stack.peek().next); // for (b|c)d, add d as child to b / c
        } else {
            if (justFinishedBrackets){
                var next = stack.pop().next;
                next.s = "" + c;
                stack.push(next);
            } else {
                var n = new Node(""+c);
                stack.peek().addChild(n);
                stack.push(n);
            }
        }
        justFinishedBrackets = (c == ')');
    }
    return root;
}

// Test it out
var str = "a(c|mo(r|l))e";
var root = createTree(str);
printTree(root, "");
// Prints: ace / amore / amole

我只更改了一行,允许处理两个以上连续的括号,并将原始翻译留在 cmets 中

我还添加了一个函数来返回结果数组,而不是打印它们...

var getTree = function(root,prefix){
  this.out = this.out || []
  prefix = prefix.replace(/\./g, "");
  for(i in root.children){
    var child = root.children[i]
    getTree(child, prefix + root.s, out);
  }
  if(root.children.length < 1){
    this.out.push(prefix + root.s);
  }
  if(!prefix && !root.s){
    var out = this.out;
    this.out = null
    return out;
  }
}

// Test it
var str = "a(b|c)d";
var root = createTree(str);
console.log(getTree(root, ""));
// logs ["abd","acd"]

最后一部分,也允许空字符串,所以...(ab|c|) 表示abcnothing,还有一个方便的快捷方式,以便将ab?c 转换为a(b|)c

var getMatches = function(str){
    str = str.replace(/(.)\?/g,"($1|)")
    // replace all instances of `(???|)` with `(???|µ)`
    // the µ will be stripped out later
    str = str.replace(/\|\)/g,"|µ)")
    // fix issues where last character is `)` by inserting token `µ`
    // which will be stripped out later
    str = str+"µ"
    var root = createTree(str);
    var res = getTree(root, "");
    // strip out token µ
    for(i in res){
        res[i] = res[i].replace(/µ/g,"")
    }
    // return the array of results
    return res
}

getMatches("a(bc|de?)?f");
// Returns: ["abcf","adef","adf","af"]

最后一部分有点hack-ish,因为它依赖于µ不在字符串中(对我来说不是问题)并解决了一个错误,其中输入字符串末尾的)导致不正确的输出,通过在每个字符串的末尾插入一个µ,然后从结果中删除它。我很乐意有人提出更好的方法来处理这些问题,以便它可以作为更通用的解决方案。

这段代码可以满足我的一切需求。感谢您的所有帮助!

【讨论】:

【解决方案2】:

我想你正在尝试用一棵树很容易(只要它只是 or 语句)。

a(b|c)d(或任何或语句)解析为如下所示的树:a 有孩子bcbc 有一个共同的孩子dbc 都可以包含 0 个或多个节点(如 c 可能是 g(e|f)h 在这种情况下(部分)树将为 a -&gt; g -&gt; e/f (2 nodes) -&gt; h -&gt; dc 可能为空,其中树的情况(部分)将是a -&gt; d,但实际的物理空节点可能会简化事情,您应该在尝试编写代码时看到)。

使用递归或堆栈生成树应该不会太困难。

一旦你有了一棵树,递归遍历整个事物并生成所有字符串就很简单了。

另外,here 是一个类似问题的链接,提供一两个库。

编辑:

"shouldn't be too difficult" - 好吧,也许不是

Here 是一个有点复杂的示例 (Java),可能需要一些有关堆栈的高级知识。

Here 是一个稍微简单的版本 (Java),这要归功于在每个 (())|( 等之间插入一个特殊字符。

请注意,这两种方法都不是特别有效,关键是为了让大家了解这个想法。

【讨论】:

  • 如果可能的话,我更喜欢 javascript,我对 javascript 非常熟悉,但我不知道如何编写解析器(不过我很想学习)。这种方法似乎是我正在寻找的答案 - 您是否有任何指示我将如何执行您的步骤一和二(创建解析树,遍历树)。例如,我有点困惑,如何创建具有共同孩子的树,然后如何遍历具有共同孩子的树。
  • 这看起来很棒 - 我相信我可以毫无问题地翻译这个 - 似乎正是我所需要的。谢谢,我会告诉你我的进展情况。
  • 我翻译了它,添加了几个方法,一切都很好 - 谢谢! stackoverflow.com/a/14810830/665261
【解决方案3】:

这是一个 JavaScript 示例,用于解析 (a|b) 和 (a|b|) 的可能性,创建一个可能的子字符串数组,并根据 this answer 组成匹配项。

var regex = /\([RLUD]*\|[RLUD]*\|?\)/, 
    str = "R(LD|DR)U(R|L|)",
    substrings = [], matches = [], str_tmp = str, find

while (find = regex.exec(str_tmp)){
  var index = find.index

  finds = find[0].split(/\|/)
  substrings.push(str_tmp.substr(0, index))

  if (find[0].match(/\|/g).length == 1) 
    substrings.push([finds[0].substr(1), finds[1].replace(/.$/, '')])
  else if (find[0].match(/\|/g).length == 2){
    substrings.push([finds[0].substr(1), ""])
    substrings.push([finds[1], ""])
  }

  str_tmp = str_tmp.substr(index + find[0].length)
}
if (str_tmp) substrings.push([str_tmp])
console.log(substrings) //>>["R", ["LD", "DR"], "U", ["R", ""], ["L", ""]]

//compose matches
function printBin(tree, soFar, iterations) {
  if (iterations == tree.length) matches.push(soFar)
  else if (tree[iterations].length == 2){
      printBin(tree, soFar + tree[iterations][0], iterations + 1)
      printBin(tree, soFar + tree[iterations][1], iterations + 1)
  }
  else printBin(tree, soFar + tree[iterations], iterations + 1)
}
printBin(substrings, "", 0)
console.log(matches) //>>["RLDURL", "RLDUR", "RLDUL", "RLDU", "RDRURL", "RDRUR", "RDRUL", "RDRU"]

【讨论】:

  • 这很有趣。当您向输入字符串添加第三个问号时,似乎有一个错误。另外,就我而言,我需要一种表达thisthat 的方法,所以我可以写LDD?R 而不是LD(D|)R 并使用相同的语法来写L(DL|RD)U(扩展为LDLU 和@987654330 @)。谢谢你的帮助。我也会在这里发布我最终使用的代码。
  • 我添加了一个答案,你可能有兴趣...stackoverflow.com/a/14810830/665261
  • @billy ... 看起来很有趣,我会从中学习。我发现了那个小错误,但我的索引树并不能真正工作超过两个“?”。不过,这是一项创造性的工作,也许我会努力让它发挥作用。
  • @BillyMoon ...我修好了!现在索引数组似乎正确地列出了选择节点的所有组合,并且映射到子字符串,可以创建正确的组合。当不同的子字符串组合成相似的字符串时,可能会发生重复。也许有一天我会得到二叉树......
  • @BillyMoon ...嗨,Billy,我保证不会再打扰您,但请在我更新的答案末尾查看构成匹配项的函数。它有 8 行长,基于 eboix 关于二进制排列的精彩回答。
猜你喜欢
  • 1970-01-01
  • 2011-10-02
  • 2015-05-05
  • 1970-01-01
  • 2016-11-04
  • 2021-10-12
  • 1970-01-01
  • 2021-06-11
  • 1970-01-01
相关资源
最近更新 更多