【问题标题】:Matching math expression with regular expression?将数学表达式与正则表达式匹配?
【发布时间】:2010-04-07 19:23:10
【问题描述】:

例如,这些是有效的数学表达式:

a * b + c
-a * (b / 1.50)
(apple + (-0.5)) * (boy - 1)

这些是无效的数学表达式:

--a *+ b @ 1.5.0  // two consecutive signs, two consecutive operators, invalid operator, invalid number
-a * b + 1)  // unmatched parentheses
a) * (b + c) / (d  // unmatched parentheses

匹配浮点数没有问题,但括号匹配有困难。任何的想法?如果有比正则表达式更好的解决方案,我也会接受。但首选正则表达式。

========

编辑:

我想对我对“已接受的答案”的选择做一些cmet,希望有相同问题并找到这个帖子的人不会被误导。

有几个我认为“接受”的答案,但我不知道哪个是最好的。所以我(几乎)随机选择了接受的答案。除了公认的答案外,我还建议阅读 Guillaume Malartre 的答案。他们都为我的问题提供了切实可行的解决方案。对于有些严格/理论的答案,请阅读 David Thornley 在接受的答案下的 cmets。正如他所提到的,Perl 对正则表达式的扩展(源自正则语言)使其“不规则”。 (我在我的问题中没有提到任何语言,所以大多数回答者都假设正则表达式的 Perl 实现——可能是最流行的实现。当我发布我的问题时我也是如此。)

如果我在上面说错了,请纠正我。

【问题讨论】:

  • 您要求做一些不可能的事情,但您没有提及您使用的语言。
  • 嗯...我真正想知道的是,是否可以使用正则表达式来做到这一点?如果不是,那么执行此操作的最佳算法是什么?我想基于通用算法而不是语言特性来实现它。
  • Ethan,正则表达式只能处理正则语言。 a^nb^nb^na^n 形式的所有字符串都是非常规语言的示例。因此, (1 + 2 * (3 + 4 * (5 + 6 * .... N-3 + N-2 * (N-1 + N-2))...) 是一个有效的算术表达式,但是不会构成常规语言(变量甚至 N)。
  • @all 回答者:我知道为这个特定任务编写解析器可能是一种更优雅的方式。但是用正则表达式实现它也是一个很好的讨论话题。
  • 现在问题似乎变成了:实现是否“常规”? :)

标签: regex parsing


【解决方案1】:

使用下推自动机匹配括号http://en.wikipedia.org/wiki/Pushdown_automaton(或只是一个堆栈;-))

堆栈解决方案的详细信息:

while (chr available)
    if chr == '(' then
      push '('
    else
      if chr == ')' then
        if stack.elements == 0 then
          print('too many or misplaced )')
          exit
        else
          pop //from stack
end while
if (stack.elements != 0)
  print('too many or misplaced(')

甚至很简单:只保留一个计数器而不是堆栈。

【讨论】:

    【解决方案2】:

    正则表达式只能用于识别正则语言。数学表达式的语言不规则;您需要实现一个实际的解析器(例如 LR)才能执行此操作。

    【讨论】:

    • “正则表达式用于正则语言”是重言式,没有意义。
    • @stereofrog:这不是一个重言式。你可能觉得太简单了,但是各种程序员连形式语言都不知道,不知道什么是上下文无关语言,什么是正则语言,以及为什么正则表达式可能不是正确的选择一个特殊的问题。
    【解决方案3】:

    用正则表达式匹配括号是很有可能的。

    这是一个 Perl 脚本,它将解析任意深度匹配括号。虽然它会抛出不匹配的括号,但我并没有专门设计它来验证括号。只要它们是平衡的,它就会解析任意深度的括号。但是,这将帮助您入门。

    关键在于正则表达式及其使用中的递归。玩它,我相信你也可以用它来标记不匹配的 prens。我认为,如果您捕获此正则表达式丢弃的内容并计算括号(即测试不匹配文本中的奇数括号),您将获得无效、不平衡的括号。

    #!/usr/bin/perl
    $re = qr  /
         (                      # start capture buffer 1
            \(                  #   match an opening paren
            (                   # capture buffer 2
            (?:                 #   match one of:
                (?>             #     don't backtrack over the inside of this group
                    [^()]+    #       one or more 
                )               #     end non backtracking group
            |                   #     ... or ...
                (?1)            #     recurse to opening 1 and try it again
            )*                  #   0 or more times.
            )                   # end of buffer 2
            \)                  #   match a closing paren
         )                      # end capture buffer one
        /x;
    
    
    sub strip {
        my ($str) = @_;
        while ($str=~/$re/g) {
            $match=$1; $striped=$2;
            print "$match\n";
            strip($striped) if $striped=~/\(/;
            return $striped;
        }
    }
    
    while(<DATA>) {
        print "start pattern: $_";
        while (/$re/g) { 
            strip($1) ;
        }
    }   
    
    __DATA__
    "(apple + (-0.5)) * (boy - 1)"
    "((((one)two)three)four)x(one(two(three(four))))"
    "a) * (b + c) / (d"
    "-a * (b / 1.50)"
    

    输出:

    start pattern: "(apple + (-0.5)) * (boy - 1)"
    (apple + (-0.5))
    (-0.5)
    (boy - 1)
    start pattern: "((((one)two)three)four)x(one(two(three(four))))"
    ((((one)two)three)four)
    (((one)two)three)
    ((one)two)
    (one)
    (one(two(three(four))))
    (two(three(four)))
    (three(four))
    (four)
    start pattern: "a) * (b + c) / (d"
    (b + c)
    start pattern: "-a * (b / 1.50)"
    (b / 1.50)
    

    【讨论】:

    • 虽然这可行,但我不会 +1,因为任何使用扩展正则表达式黑客来匹配嵌套括号的人都会感到头疼。换一种方式做会好得多。
    • Perl 对正则表达式语言的扩展使它们不再是 regular 表达式。
    • @chris:这不是“hack”,它直接来自 Perl perlre 手册页。我唯一做的是将尖括号更改为 prens 并添加了外部捕获组。我写了递归子程序。从什么时候开始递归和使用 Perl 的文档化特性是一种“hack”?
    • @drewk - 正则表达式语法的扩展是一种 hack。他们正在破解一个工具(正则表达式)来做它不是为它设计的,也不是特别适合的事情。恕我直言,如果您必须评论正则表达式,则不应使用单个正则表达式来完成任务。
    • @drewk - “Perl 5.10 正则表达式扩展使它们不规则怎么办?”做到这一点的能力。 “正则”表达式只解析“正则”语言,描述平衡括号的语言比“正则”更复杂(它是上下文无关的)。因此,任何可以解析它们的东西都比单纯的“正则”表达式更复杂,这就是 Perl 6 文档专门使用术语“正则表达式”的原因。
    【解决方案4】:

    我相信你最好实现一个真正的解析器来完成你所追求的。

    简单数学表达式的解析器是“Parsing 101”,网上有几个例子可以找到。

    一些例子包括:

    请注意,验证表达式所需的语法比上面的示例更简单,因为这些示例还实现了表达式的求值。

    【讨论】:

    • LALR(1) 语法对此太过分了。该问题描述了一种与上下文无关的语言,并且可以通过对常规表达的相对较小的补充来识别。
    • 这取决于,IMO。解析器语法可能比复杂的正则表达式更容易维护和扩展。
    • Pyparsing 不再托管在 wikispaces.com 上。转至github.com/pyparsing/pyparsing
    【解决方案5】:

    你不能使用正则表达式来做平衡括号之类的事情。

    【讨论】:

    • 不,你不能。如果存在可以确保括号平衡的表达式,则它们不是正则表达式。
    • @drewk:“下面的帖子”在这里没有意义,因为答案的顺序是可变的。请将其标识为“X 的答案”或类似名称。
    • 我认为 jemfinch 提到了 Victor Hurdugaci 的帖子。我正在阅读 Victor 提供的链接。我很困惑得到两种相反的答案。
    • @Ethan:Victor 的帖子不是关于使用正则表达式,而是一个下推自动机,它可以处理平衡的表达式。您无法使用正则表达式检查平衡括号,但您可以通过一些增强功能来检查。 Victor 给出了一种增强(理论上更简单),而 drawk 给出了另一种(使用一些 Perl 对正则表达式的扩展)。
    • 有些正则表达式实现支持递归模式(如 PCRE 或 .NET)。但就形式语言理论而言,这些都不是正则表达式。
    【解决方案6】:

    这对于单个正则表达式很棘手,但使用混合正则表达式/过程方法很容易。这个想法是为简单表达式(不带括号)构造一个正则表达式,然后用一些原子字符串(例如标识符)重复替换( simple-expression )。如果最终简化的表达式匹配相同的“简单”模式,则原始表达式被认为是有效的。

    插图(在 php 中)。

    function check_syntax($str) {
    
        // define the grammar
        $number = "\d+(\.\d+)?";
        $ident  = "[a-z]\w*";
        $atom   = "[+-]?($number|$ident)";
        $op     = "[+*/-]";
        $sexpr  = "$atom($op$atom)*"; // simple expression
    
        // step1. remove whitespace
        $str = preg_replace('~\s+~', '', $str);
    
        // step2. repeatedly replace parenthetic expressions with 'x'
        $par = "~\($sexpr\)~";
        while(preg_match($par, $str))
            $str = preg_replace($par, 'x', $str);
    
        // step3. no more parens, the string must be simple expression
        return preg_match("~^$sexpr$~", $str);
    }
    
    
    $tests = array(
        "a * b + c",
        "-a * (b / 1.50)",
        "(apple + (-0.5)) * (boy - 1)",
        "--a *+ b @ 1.5.0",
        "-a * b + 1)",
        "a) * (b + c) / (d",
    );
    
    foreach($tests as $t)
        echo $t, "=", check_syntax($t) ? "ok" : "nope", "\n";
    

    上面只验证语法,但同样的技术也可以用来构造一个真正的解析器。

    【讨论】:

    • +1 很好的插图。小问题:我认为您不能这么早删除空格,因为它可以将无效表达式转换为有效表达式。例如:f o o + 1 变为 foo+1
    • @FM:好点。这当然有点懒惰,在现实世界中,应该在语法中添加空格,例如expr = atom (ws* op ws* atom)*
    【解决方案7】:

    对于括号匹配和实现其他表达式验证规则,编写自己的小解析器可能是最简单的。正则表达式在这种情况下不好用。

    【讨论】:

    • 我实际上会在 Perl 或 Python 中尝试 eval - 似乎是最快的测试方法。该表达式无论如何都要被评估,所以为什么不:try { worked = true; evaluate() } catch (...) { worked = false; }
    【解决方案8】:

    好的,这是我在 ActionScript3 中查找括号的版本,使用这种方法可以极大地促进分析括号之前、括号内和括号之后的部分,如果最后仍有一些括号,您可以发出警告或拒绝发送到最终评估函数。

    package {
    import flash.display.Sprite;
    import mx.utils.StringUtil;
    public class Stackoverflow_As3RegexpExample extends Sprite
    {
        private var tokenChain:String = "2+(3-4*(4/6))-9(82+-21)"
        //Constructor
        public function Stackoverflow_As3RegexpExample() {
            // remove the "\" that just escape the following "\" if you want to test outside of flash compiler.
            var getGroup:RegExp = new RegExp("((?:[^\\(\\)]+)?)   (?:\\()       (  (?:[^\\(\\)]+)? )    (?:\\))        ((?:[^\\(\\)]+)?)", "ix")   //removed g flag
            while (true) {
                tokenChain = replace(tokenChain,getGroup)
                if (tokenChain.search(getGroup) == -1) break; 
            }
            trace("cummulativeEvaluable="+cummulativeEvaluable)
        }
        private var cummulativeEvaluable:Array = new Array()
        protected function analyseGrammar(matchedSubstring:String, capturedMatch1:String, capturedMatch2:String,  capturedMatch3:String, index:int, str:String):String {
            trace("\nanalyseGrammar str:\t\t\t\t'"+str+"'")
            trace("analyseGrammar matchedSubstring:'"+matchedSubstring+"'")
            trace("analyseGrammar capturedMatchs:\t'"+capturedMatch1+"'  '("+capturedMatch2+")'   '"+capturedMatch3+"'")
            trace("analyseGrammar index:\t\t\t'"+index+"'") 
            var blank:String = buildBlank(matchedSubstring.length)
            cummulativeEvaluable.push(StringUtil.trim(matchedSubstring))
            // I could do soo much rigth here!
            return str.substr(0,index)+blank+str.substr(index+matchedSubstring.length,str.length-1)
        }
        private function replace(str:String,regExp:RegExp):String {
            var result:Object = regExp.exec(str)
            if (result)
                return analyseGrammar.apply(null,objectToArray(result)) 
            return str
        }
        private function objectToArray(value:Object):Array {
            var array:Array = new Array()
            var i:int = 0
            while (true) {
                if (value.hasOwnProperty(i.toString())) {
                    array.push(value[i])
                } else {
                    break;
                }
                i++
            }
            array.push(value.index)
            array.push(value.input)
            return array
        }
        protected function buildBlank(length:uint):String {
            var blank:String = ""
            while (blank.length != length)
                blank = blank+" "
            return blank
        }
    }
    

    }

    它应该跟踪这个:

    analyseGrammar str:             '2+(3-4*(4/6))-9(82+-21)'
    analyseGrammar matchedSubstring:'3-4*(4/6)'
    analyseGrammar capturedMatchs:  '3-4*'  '(4/6)'   ''
    analyseGrammar index:           '3'
    
    analyseGrammar str:             '2+(         )-9(82+-21)'
    analyseGrammar matchedSubstring:'2+(         )-9'
    analyseGrammar capturedMatchs:  '2+'  '(         )'   '-9'
    analyseGrammar index:           '0'
    
    analyseGrammar str:             '               (82+-21)'
    analyseGrammar matchedSubstring:'               (82+-21)'
    analyseGrammar capturedMatchs:  '               '  '(82+-21)'   ''
    analyseGrammar index:           '0'
    cummulativeEvaluable=3-4*(4/6),2+(         )-9,(82+-21)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-16
      • 2014-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多