正则表达式删除涉及零或一的冗余乘法/除法?

时间:2015-03-02 03:56:37

标签: javascript regex

我有一个奇怪的问题,我接收计算机生成的方程式(作为一个字符串),其中偶尔存在零或一和零的乘法/除法。这些方程将以字符串形式呈现给用户。

我知道我可以通过实现一种解析器来删除等式中的这些冗余部分,但我很好奇是否可以使用正则表达式来删除它们。

在我最终放弃了(非常有限的)正则表达式技能之前,我想出了以下内容:

/([^\+\-]*(?:0|0\*|\*0){1,}[^\+\-]*)|(?:1\*|\*1)/g

它似乎只在以下情况下有用:

  • 没有非零数字,其中有零(即没有10年代,20年代等)。
  • 没有否定。

它也不适用于括号。不幸的是,括号很常见。

请注意,删除上述冗余部分可能会导致冗余括号或“零”括号(即它可能会变成()*x,相当于0*x)。冗余括号不是一个问题,但我认为可以通过类似于第一次查找()的第二次传递来删除“零”括号。如果其中任何一个都可以在与解决问题的正则表达式相同的正则表达式中完成,那么我会留下非常深刻的印象。

所以我转向你的Stack Overflow正则大师。可以吗?

假设

对于字符串化方程,可以假设如下:

  • 没有除零,方程式不会出现[expr]/0甚至是[expr]/0评估为[expr]/sin(0)的表达式,例如+
  • 方程式中唯一的运算符本身是- * /-
  • 减号运算符(sin)包括减法否定,尽管否定是 始终 括在括号内。
  • 上述以外的任何操作(cospow^等)都将显示为函数调用。 (没有% "(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)" 等)

样本公式

{{1}}

不是很乱吗?

2 个答案:

答案 0 :(得分:3)

在发表评论后,我无法抗拒它:)

你最大的问题是嵌套括号。正则表达式本身在处理嵌套结构时非常糟糕。这是我的口头禅的一个主要例子“正则表达式是工具,而不是解决方案”

以正则表达式作为工具,您可以为此树结构应用一种“叶子优先”(或自下而上)方法,这就是我在第一部分while (sEq.match(...)) {...}中所做的。之后,我可以浏览创建的array并进行一些简单的文本编辑。

我还补充说,1**1/1会被删除,因为它们同样不会影响等式。您可以扩展它以使其足够智能,分别用sin(0)cos(0)替换0 / 1,然后在某些情况下解决方案会更小。

(如代码注释中所述,如果等式包含5.0*4等内容,则会中断,因为JavaScript正则表达式没有lookbehind所以我相信\b字边界为我做这项工作。只需添加删除不必要的小数的逻辑就可以解决这个问题。像sEq = sEq.replace(/\.0+\b/g, '');这样的东西,但我不知道你的用例是否有必要。) 编辑:现在已修复,5.0*4应保持原状

虽然没有经过彻底的测试,欢迎回复。

var sEq = "(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)";

var aParts = [];

document.getElementById('output').value = sEq + '\n';

while (sEq.match(/\([^()]*\)/)) {
  // while there are still "leafs", save them to aParts and replace with
  // a reference to their index in aParts, making their parent a new
  // "leaf" because it now doesn't contain the round brackets anymore
  sEq = sEq.replace(/([a-z]*)\(([^()]*)\)/gi, function(m, f, a) {
    var n = aParts.length;
    aParts[n] = {
      'found':m,
      'funct':f,
      'arith':a
    };
    return '[' + n + ']';
  });
}

for (var i = 0; i < aParts.length; i++) {

  // isolate divisions/multiplications
  var dms = aParts[i]['arith'].split(/(?=[+-])/);

  for (var j = 0; j < dms.length; j++) {
    // if the isolated part is multiplied by or divided into 0, replace with 0
    if (dms[j].match(/([^.]|^)\b0[*\/]|\*0(?!\.?0*[1-9])/)) {
      dms[j] = dms[j].replace(/([+-]?).*/, '$1'+'0');
    }
    // remove /1, *1 and 1*
    dms[j] = dms[j].replace(/[\/*]1\b(?!\.0*[1-9])(?:\.0*)?/g, '').replace(/([^.]|^)\b1\*/g, '$1');
  }
  
  // join back together, remove 0+, +0 and -0; 0- results in negation
  aParts[i]['arith'] = dms.join('').replace(/[+-]0(?!\.?0*[1-9])(?:\.?0*)?/g, '').replace(/([^.]|^)\b0(?:\+|(-))/g, '$1$2');
  
  // if after this the part contains just 0, perpetuate down to further eliminate
  if (aParts[i]['funct']=='' && aParts[i]['arith']=='0') {
    for (var j = i + 1; j < aParts.length; j++) {
      if (aParts[j]['arith'].indexOf('[' + i + ']') != -1) {
        aParts[j]['arith'] = aParts[j]['arith'].replace('[' + i + ']', '0');
        break;
      }
    }
  }
  // add back parts previously simplified by replacing [n] with the content of aParts[n]
  aParts[i]['arith'] = aParts[i]['arith'].replace(/\[(\d+)\]/g, function (m, n) {
    return aParts[parseInt(n)]['funct'] + '(' + aParts[parseInt(n)]['arith'] + ')';
  });
  
  // This is just to show the progress of the algorithm
  document.getElementById('parts').value += i + '\t' + aParts[i]['found'] + '\n';
  var tmp = [];
  for (var a = 0; a < aParts.length; a++) {
    tmp[a] = {
      'funct':aParts[a]['funct'],
      'arith':aParts[a]['arith'].replace(/\[(\d+)\]/g, function (m, n) {
        return tmp[parseInt(n)]['funct'] + '(' + tmp[parseInt(n)]['arith'] + ')';
      })
    };
  }
  // some steps didn't change after analysing, only append when significant
  if (document.getElementById('output').value.indexOf('\n' + tmp[tmp.length-1]['arith'] + '\n') ==-1)
    document.getElementById('output').value += tmp[tmp.length-1]['arith'] + '\n';
}

document.getElementById('solution').innerHTML =
  aParts[aParts.length-1]['funct'] +
  '(' + aParts[aParts.length-1]['arith'] + ')';
<h3>Parts isolated:</h3>
<textarea id="parts" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Steps that simplified the equation:</h3>
<textarea id="output" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Solution:</h3>
<pre id="solution"></pre>

答案 1 :(得分:2)

我最终实现了一个完全非正则表达式的递归方法来解决问题。 cleanupEqn()函数实际上是通过运算符对每个字符串进行拆分(顶级括号被分组为单个操作数),对每个子部分进行递归操作,然后在备份函数调用链的过程中执行另一个操作。

Comparing this with funkwurm's regex solution in jsperf显示它明显更快(至少在我的个人Chrome和Firefox浏览器中)。

它还没有经过全面测试,我确信可以进行改进,所以我欢迎任何反馈。

窃取funkwurm的片段显示以显示我的解决方案:

&#13;
&#13;
var sEq = "(0+(0/0.5+(0+1*cos(p)+0*0+0*sin(p))*cos(k)+(0+1*0+0*1+0*0)*(-sin(k))+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*x+(0+(0+1*cos(p)+0*0+0*sin(p))*sin(k)+(0+1*0+0*1+0*0)*cos(k)+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*0)*y+(0+(0+1*cos(p)+0*0+0*sin(p))*0+(0+1*0+0*1+0*0)*0+(0+1*(-sin(p))/0.5+0*0+0*cos(p))*1)*z)";

var operators = ['+','-','*','/'];
var level = 0;

var result = cleanupEqn(sEq);

document.getElementById('solution').innerHTML = result;

function cleanupEqn(eqn){
    var parts = removeRedundant(splitByParen(eqn));
    level++;
    document.getElementById('output').value += 'Level ' + level + ': Processing ' + eqn + '\n';
    for(var i=0; i < parts.length; i++){
        document.getElementById('parts').value += parts[i] + '\n';
        if(parts[i].charAt(0) === '('){
            // Clean up the expression inside the parentheses
            var tmp = cleanupEqn(parts[i].substring(1,parts[i].length-1));

            // If it was reduced to a zero, don't add the parentheses back
            if(tmp === '0'){
                parts[i] = '0';
            }
            else {
                parts[i] = '(' + tmp + ')';
            }
        }
    }
    // Finally, remove redundancies again, since some might have bubbled up.
    removeRedundant(parts);
    
    document.getElementById('output').value += 'Level ' + level + ': Completed ' + eqn + '\n' + JSON.stringify(parts, null, '\t') + '\n';
    level--;
    // Join it all into a string and return
    return parts.join('');
}

function splitByParen(str){
    var out = [];
    var exprStart = 0;
    var count = 0;
    var i;
    for (i = 0; i < str.length; i++) {
        var t = str.charAt(i);
        if(str.charAt(i) === '('){
            if(i > exprStart && count === 0){
                out.push(str.substring(exprStart, i));
                exprStart = i;
            }
            count++;
        }
        else if(str.charAt(i) === ')'){
            count--;
            if(count === 0){
                out.push(str.substring(exprStart, i+1));
                exprStart = i+1;
            }
        }
        else if(count === 0 && operators.indexOf(str.charAt(i)) > -1){
            if(i > exprStart){
                out.push(str.substring(exprStart, i));
            }
            out.push(str.charAt(i));
            exprStart = i+1;
        }
    }
    
    // Add the last part
    if(i > exprStart){
        out.push(str.substring(exprStart, i));
    }
    return out;
}

function removeRedundant(parts) {
    for(var i=0; i < parts.length; i++){
        if(parts[i] === '0'){
            if(i === 0){
                switch(parts[i+1]){
                    case '*':
                    case '/':
                        if(parts[i+1] === '*' || parts[i+1] === '/'){
                            parts.splice(i, 3, '0');
                        }
                        else {
                            parts.splice(i, 2);
                        }
                        i--;
                        break;
                    case '+':
                        parts.splice(i, 2);
                        i--;
                        break;
                    case '-':
                        parts.splice(i, 1);
                        i--;
                }
            }
            else {
                switch(parts[i-1]){
                    case '*':
                        if(parts[i+1] === '*' || parts[i+1] === '/'){
                            // Check if the prior portion is part of a function call
                            if(i > 2 && operators.indexOf(parts[i-3]) < 0){
                                // Check if the next portion is part of a function call (or undefined)
                                if(i+3 < parts.length && operators.indexOf(parts[i+3]) < 0){
                                    parts.splice(i-3, 6, '0');
                                    i -= 4;
                                }
                                else {
                                    parts.splice(i-3, 5, '0');
                                    i -= 4;
                                }
                            }
                            else {
                                parts.splice(i-2, 4, '0');
                                i -= 3;
                            }
                        }
                        else {
                            parts.splice(i-2, 3, '0');
                            i -= 3;
                        }
                        break;
                    case '+':
                    case '-':
                        if(parts[i+1] === '*' || parts[i+1] === '/'){
                            // Check if the next portion is part of a function call (or undefined)
                            if(i+3 < parts.length && operators.indexOf(parts[i+3]) < 0){
                                parts.splice(i, 4, '0');
                            }
                            else {
                                parts.splice(i, 3, '0');
                            }
                            i--;
                        }
                        else if(parts[i+1] === '+'){
                            parts.splice(i-1, 2);
                            i -= 2;
                        }
                        else {
                            parts.splice(i-1, 2);
                            i -= 2;
                        }
                }
            }
        }
        else if(parts[i] === '1'){
            if(i === 0){
                switch(parts[i+1]){
                    case '*':
                        parts.splice(i, 2);
                        i--;
                        break;
                    case '+':
                    case '-':
                        if(parts[i+1] === '*'){
                            parts.splice(i, 2);
                            i--;
                        }
                }
            }
            switch(parts[i-1]){
                case '*':
                case '/':
                    if(parts[i+1] !== '/'){
                        parts.splice(i-1, 2);
                        i -= 2;
                    }
                    break;
                case '+':
                case '-':
                    if(parts[i+1] === '*'){
                        parts.splice(i, 2);
                        i--;
                    }
            }
        }
    }
    return parts;
}
&#13;
<h3>Parts isolated:</h3>
<textarea id="parts" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Steps that simplified the equation:</h3>
<textarea id="output" rows="10" style="width:100%" wrap="off"></textarea>
<h3>Solution:</h3>
<pre id="solution"></pre>
<script src="new.js"></script>
&#13;
&#13;
&#13;