中缀表格支持负数

时间:2015-09-03 10:10:05

标签: ruby infix-notation

我正试图解决一个问题:

Instructions

Given a mathematical expression as a string you must return the result as a number.

Numbers

Number may be both whole numbers and/or decimal numbers. The same goes for the returned result.

Operators

You need to support the following mathematical operators:

Multiplication *
Division /
Addition +
Subtraction -
Operators are always evaluated from left-to-right, and * and / must be evaluated before + and -.

Parentheses

You need to support multiple levels of nested parentheses, ex. (2 / (2 + 3.33) * 4) - -6

Whitespace

There may or may not be whitespace between numbers and operators.

An addition to this rule is that the minus sign (-) used for negating numbers and parentheses will never be separated by whitespace. I.e., all of the following are valid expressions.

1-1    // 0
1 -1   // 0
1- 1   // 0
1 - 1  // 0
1- -1  // 2
1 - -1 // 2

6 + -(4)   // 2
6 + -( -4) // 10
And the following are invalid expressions

1 - - 1    // Invalid
1- - 1     // Invalid
6 + - (4)  // Invalid
6 + -(- 4) // Invalid
Validation

从而使'2 /2+3 * 4.75- -6'成为有效的表达式。我已经能够为表达式编写抛光表单,这些表达式不尊重空格但不给出负数。如果他们尊重空白,我想我可以解决负数的问题。我的问题是如果不尊重空格并给出负数,如何实际标记输入表达式。到目前为止,这是我的算法:

def is_operator? s
  operators = ['*', '/', '+', '-']
  operators.include?(s)
end

def is_operand? s
    !(s =~ /^[0-9.]+$/).nil?
end

def priority op
  case op
    when "(" , ")" then 0
    when "/", "*" then 2
    else 1
  end
end

def eval(lt,rt,op)
  case op
    when '+' then lt.to_f + rt.to_f 
    when '-' then lt.to_f - rt.to_f  
    when '*' then lt.to_f * rt.to_f  
    when '/' then lt.to_f / rt.to_f  
  end
end

def indent_string s
    s.gsub(/[^[0-9.]]/) { |m| " #{m} "}.split(" ")
end

def create_polish s
    stack = Array.new()
    array = indent_string s
    fpp = ""
    array.each do |item|
        if is_operand? item
            fpp = fpp + item + " "
        else
            if item == '('
                stack << item
            else if is_operator? item
                    while stack.any? && ( priority(stack[-1]) >= priority(item) )
                        fpp = fpp + stack.pop + " "
                    end
                    stack << item
                 else
                    while stack.any? && !(stack[-1] == '(' )
                        fpp = fpp + stack.pop + " "
                    end
                    stack.pop
                 end
            end
        end
    end
    while stack.any?
        fpp = fpp + stack.pop + " "
    end
    fpp
end

def solve_polish s
  stack = Array.new()
  s.split(" ").each do |item|
    unless is_operator? item 
      stack << item
    else
      elements = stack.pop(2)
      stack << eval(elements[0], elements[1], item)
    end
  end
  puts stack.pop
end

solve_polish(create_polish '(5 + 2) * 9 - 10 + ( 7 * (2 + 3) ) - 3 * (2)')

它解决了不符合空格规则的非负表达式,因为我创建了indent_string方法,它在每个运算符之前和之后放置一个空格,然后我只是拆分字符串来获取标记。这样我遗憾地失去了负数。有什么想法吗?

更新1 :想到这一点,我需要一个正则表达式,如果后面没有其他操作员,则前后空格。所以'2-2'会在'2 - -2'中转换,因为第二个' - '在他之前有一个' - '而不是另一个数字。

1 个答案:

答案 0 :(得分:1)

你有解析代码,基本上循环遍历符号并尝试识别每个标记。表达式处理的“堆栈”部分很复杂,但您识别每个令牌的方式非常简单。

您需要调整此标记以使用“状态机”。在每个点上,根据机器的当前状态,您需要一组不同的可能的下一个令牌。您可以使用当前可能的下一个令牌集来帮助您识别下一个令牌是什么。您成功识别的每个令牌也可能具有更改机器状态的后果。如果下一个令牌不能是基于当前状态的任何可能令牌,则表示您有解析错误。

幸运的是,你的情况是最简单的。您可能希望执行以下操作:从EXPRESSION_EXPECTED状态开始。您只想阅读左括号或数字。如果您阅读了一个“号码”,请转到OPERATOR_EXPECTED州。如果您阅读左括号,请递归读取整个内部表达式。当您到达结束括号时,请转到OPERATOR_EXPECTED州。

现在,当您处于OPERATOR_EXPECTED状态时,您唯一乐意阅读的是操作员。一读完一个,就会回到EXPRESSION_EXPECTED状态。 (这里的运算符表示二元运算符,而不是一元减号。)

您可以测试此方案,而不必担心减号,并确保您可以解析代码当前解析的相同内容。

现在,如果你在OPERATOR_EXPECTED,减号意味着'减去'并且是一个运算符。如果您在EXPRESSION_EXPECTED,则减号表示“减号”,这是读取数字的第一部分。

这个概念对解析至关重要。您的问题没有用BNF表示,BNF是一种描述语法的标准语言,但BNF适用于有限状态机。关于这些东西还有很多很多的计算机科学理论,其中一些很复杂,但很多都可以使用。