如何在数学解析器(ruby)中实现加法运算符

时间:2015-01-22 15:39:19

标签: ruby parsing math

我正在尝试在ruby中为数学表达式构建自己的求值器,在此之前我正在尝试实现解析器将表达式分解为树(数组)。它正确地用括号分解表达式,但是我在试图弄清楚如何使用运算符优先级来正确分解表达式时遇到了很多麻烦。

现在,像1+2*3+4这样的字符串变为1+[2*[3+4]]而不是1+[2*3]+4。我正在努力做最简单的解决方案。

这是我的代码:

@d = 0
@error = false
#manipulate an array by reference
def calc_expr expr, array
    until @d == expr.length
        c = expr[@d]
        case c 
        when "("
            @d += 1
            array.push calc_expr(expr, Array.new)
        when ")"
            @d += 1
            return array
        when /[\*\/]/
            @d +=1
            array.push c
        when /[\+\-]/
            @d+=1
            array.push c
        when /[0-9]/
            x = 0
            matched = false
            expr[@d]
            until matched == true
                y = expr.match(/[0-9]+/,@d).to_s
                case expr[@d+x]
                when /[0-9]/
                    x+=1
                else matched = true
                end
            end
            array.push expr[@d,x].to_i
            @d +=(x)
        else 
            unless @error
                @error = true
                puts "Problem evaluating expression at index:#{@d}"
                puts "Char '#{expr[@d]}' not recognized"
            end
            return
        end
    end

    return array
end
@expression = ("(34+45)+(34+67)").gsub(" ","")
evaluated = calc @expression
puts evaluated.inspect

2 个答案:

答案 0 :(得分:2)

如果您确实需要自己解析表达式,那么您应该搜索表达式的两面(例如' 2 * 3')并将其替换为您的答案(如果您正在尝试计算答案)或表达式对象(如数组树,如果要保留表达式的结构并在以后求值)。如果按优先顺序执行此操作,则将保留优先级。

作为简化示例,您的表达式解析器应该:

  • 反复搜索所有内部的parens:/(([^)+]))/并用$ 1的表达式解析器调用替换它(抱歉丑陋的正则表达式:)

    现在所有的parens都消失了,所以你正在研究数字和/或表达式对象之间的数学运算 - 对它们进行相同的处理

  • 搜索乘法:/(expr | number)*(expr | number)/ 将其替换为答案或将两个表达式封装在中 一个新的表达。再次,取决于你现在是否需要答案或 如果你需要表达式树。

  • 寻找补充:......等......

如果您现在正在计算答案,那么这很容易,每次调用表达式解析器最终(在必要的递归之后)返回一个数字,您可以用它替换原始表达式。如果你想构建表达式树,以及你如何处理字符串和表达式对象的混合以便你可以在它上运行正则表达式取决于你,你可以编码指向表达式对象的指针,这是一个不同的故事在字符串中或者用一个对象数组替换外部的整个字符串,并使用与regexp类似的东西来搜索数组。

你还应该考虑与一元运营商打交道:" 3 * + 3" (如果您采取的第一步是将所有数字转换为仅包含数字的简单表达式对象,则可能会简化操作,您可能能够在此处理一元运算符,但这可能涉及棘手的情况,例如" - 3 ++ 1&#34)

或者只是按照建议找到解析库的表达式。 :)

答案 1 :(得分:2)

为了好玩,这里有一个有趣的基于正则表达式的“解析器”,它使用@DavidLjungMadison提出的漂亮的“由内而外”的方法。它首先执行简单的“a * b”乘法和除法,然后是“a + b”加法和减法,然后展开括号(a)中剩下的任何数字,然后重新开始。

为了简单起见,我只选择支持整数;将每个-?\d+扩展为更健壮的内容,并将.to_i替换为.to_f,这样就可以使用浮点值。

module Math
  def self.eval( expr )
    expr = expr.dup
    go = true
    while go
      go = false
      go = true while expr.sub!(/(-?\d+)\s*([*\/])\s*(-?\d+)/) do
        m,op,n = $1.to_i, $2, $3.to_i
        op=="*" ? m*n : m/n
      end
      go = true while expr.sub!(/(-?\d+)\s*([+-])\s*(-?\d+)/) do
        a,op,b = $1.to_i, $2, $3.to_i
        op=="+" ? a+b : a-b
      end
      go = true while expr.gsub!(/\(\s*(-?\d+)\s*\)/,'\1')
    end
    expr.to_i
  end
end

这里有一些测试:

tests = {
  "1"                            => 1,
  "1+1"                          => 2,
  "1 + 1"                        => 2,
  "1 - 1"                        => 0,
  "-1"                           => -1,
  "1 + -1"                       => 0,
  "1 - -1"                       => 2,
  "2*3+1"                        => 7,
  "1+2*3"                        => 7,
  "(1+2)*3"                      => 9,
  "(2+(3-4) *3 ) * -6 * ( 3--4)" => 42,
  "4*6/3*2"                      => 16
}

tests.each do |expr,expected|
  actual = Math.eval expr
  puts [expr.inspect,'=>',actual,'instead of',expected].join(' ') unless actual == expected
end

请注意,我在运算符上使用sub!而不是gsub!,以便在最后一个测试用例中存活。如果我使用了gsub!,则"4*6/3*2"首先会转为"24/6",从而产生4,而不是正确的展开"24/3*2"→{{1} }→"8*2"