最近补习数据结构(谁叫我不是科班出身呢),看了栈的应用之一,做算术表达式解析计算,但那个例子中没有把括号的逻辑加进去,苦想一段时间后还是没有结果,于是Google出一些表达式解析的方案,觉得用后缀表达式解析是最方便的,遂用Ruby实现了一个,支持由数字、加减乘除和括号组成的表达式,因为主要目的是对栈的应用,所以一些地方能简则简。
class MyParser
PRECEDENCE = [
['(', ')'],
['|'],
['&'],
['+', '-'],
['*', '/']
]
REG_VARIABLE = /\d+(?:\.\d+)?/
REG_OPERATOR = /[\+\-\*\/\|\&]/
REG_PARENTHESIS = /[\(\)]/
REG_SPACE = /\s+/
def self.exec(exp)
exec_postfix parse_to_postfix(exp)
end
# 将表达式解析成后缀表达式,这里后缀表达式改用数组
def self.parse_to_postfix(exp)
postfix_exp, stack = [], []
# 每次从exp中取出第一段标志符(数字,操作符,括号),进行分析,exp中的标志符取完后退出循环
until exp.nil? or exp.size == 0
case exp
when /^(#{REG_VARIABLE})/ # 数字
token = $1
postfix_exp << token
when /^(#{REG_OPERATOR})/ # 操作符
token = $1
# 这个if和else可以合在一起的,但我觉得这样逻辑更清晰,脑袋笨了冒得法啊
if stack.empty?
stack << token
else
# 原来这里的逻辑错了,现已改正
# 当读取的操作符优先级不高于栈中的操作符优先级时,需要依次将栈中的操作符弹出送到表达式中
while stack.size > 0 && precedence(token) <= precedence(stack.last)
postfix_exp << stack.pop
end
stack << token
end
when /^(#{REG_PARENTHESIS})/ # 括号
token = $1
case token
when '('
stack << token
when ')'
postfix_exp << stack.pop while !stack.empty? && stack.last != '('
# 因为一般')'都会有匹配的'(',正常情况下这里stack的最后一个元素一定是'('
raise "mismatched parenthesis '#{token}'" if stack.last != '('
stack.pop
else
raise "parenthesis '#{token}' wrong"
end
when /^(#{REG_SPACE})/ # 空格,这个只是额外处理,和表达式转换的核心逻辑无关
token = $1
else
raise "unknown token! expression is '#{exp}'"
end
exp = exp[token.size..-1]
end
until stack.empty?
# 检查括号是否匹配的,因为stack中只会压入'(',不会压入')'
# 如果表达式括号匹配的话,到这里stack中所有的'('应该都已经被处理了
raise "mismatched parenthesis '('" if stack.last == '('
postfix_exp << stack.pop
end
postfix_exp
end
# 根据后缀表达式计算结果
def self.exec_postfix(postfix_exp)
stack = []
postfix_exp.each do |token|
case token
when REG_VARIABLE # 变量直接压入stack
stack << token.to_f
when REG_OPERATOR # 碰到操作符,从stack中弹出两个数,进行计算,再将结果压入
raise 'stack size < 2'if stack.size < 2
d2, d1 = stack.pop, stack.pop
stack << exec_exp(d1, token, d2)
end
end
raise 'final stack size must be 1' if stack.size != 1
stack.first
end
# 计算操作符的优先级,返回一个数字,数字越大优先级越高
def self.precedence(op)
re = PRECEDENCE.index { |group| group.include?(op) }
raise "unknown operator '#{op}'" if re.nil?
re
end
# 输入两个数和二元运算符,进行计算,返回结果
# 例:exec_exp(1, '+', 2) => 3
def self.exec_exp(d1, op, d2)
case op
when REG_OPERATOR
d1.send(op, d2)
else
raise "operator '#{op}' wrong"
end
end
end
# 测试一下,看算的对不对
puts 'TEST CALCULATE'
[
['1+2*3', 7.0], # 前面是表达式,后面是期望的计算结果,下同
['1*2-3', -1.0],
['10-(4-2)*3', 4.0],
['2*(24-(3*10))+11', -1.0]
].each do |exp, result|
puts exp
postfix_exp = MyParser.parse_to_postfix(exp)
puts " #{'parse'.ljust(9)} = #{postfix_exp}"
puts " #{'calc'.ljust(9)} = #{MyParser.exec_postfix postfix_exp}"
puts " #{'expect'.ljust(9)} = #{result}"
end
# 测试后缀表达式是否正确
puts 'TEST POSTFIX EXPRESSION'
[
['1|2&3+4|5', '1234+&|5|'],
['1-2|3*4&5', '12-34*5&|'],
['(1|2)&3+(4|5)', '12|345|+&']
].each do |exp, result|
puts exp
postfix_exp = MyParser.parse_to_postfix(exp).join
puts " #{'parse'.ljust(9)} = #{postfix_exp}"
puts " #{'expect'.ljust(9)} = #{result}"
end
运行 结果如下:
1+2*3
parse = ["1", "2", "3", "*", "+"]
calc = 7.0
expect = 7.0
1*2-3
parse = ["1", "2", "*", "3", "-"]
calc = -1.0
expect = -1.0
10-(4-2)*3
parse = ["10", "4", "2", "-", "3", "*", "-"]
calc = 4.0
expect = 4.0
2*(24-(3*10))+11
parse = ["2", "24", "3", "10", "*", "-", "*", "11", "+"]
calc = -1.0
expect = -1.0
嗯,至少没算错……
下面说说大概思路:
基本逻辑就是两步,先把输入的表达式(我们一般的表达式写法都是中缀表达式)转换成后缀表达式,然后用后缀表达式进行计算。比如这样:
# 中缀表达式,我们通常写的形式
1*2+3
# 这是后缀表达式的写法,基本上是把运算符放到数字后面,
# 读法为从前往后读,碰到运算符,就把运算符前的两个数字代入计算,计算结果就取代了原来的两个数字和运算符,以此类推
12*3+
# 再看个复杂点的,中缀表达式如下
1+2*3
# 后缀表达式,
123*+
# 这个是有括号的中缀表达式
(1+2)*3
# 换成后缀表达式就是这样,可以看出,后缀表达式是没有括号和运算符优先级的,比较方便由计算机处理
这正是要转换成后缀表达式的原因
12+3*
后缀表达式很容易被计算机处理,逻辑也不复杂。
其实数据结构教程里的示例代码的原理和上面的Ruby代码十分类似,只是一个直接计算,一个是转换成后缀表达式。
个人感觉数据结构的示例,把对栈的操作和数据计算混在一起,比较乱。而且要处理括号的逻辑,可能要采用递归才能实现,比较麻烦。
参考资料
这篇文章是思路来源,我用Ruby重新实现了一把,补充一些细节,里面讲表达式转换逻辑和后缀表达式计算逻辑比较详细
利用堆栈解析算术表达式
这篇是我原来比较倾向的思路,完全采用递归,将表达式不停拆分解析,直到最基本的元素,可惜代码太长,比较复杂,懒得看了
解析表达式--Java编程艺术
这是RednaxelaFX
童鞋补充的资料,Wikipedia上表达式转换的文章,感叹一句:砖业人士啊
Shunting-yard algorithm
分享到:
相关推荐
这是因为栈数据结构可以帮助我们实现后缀表达式(逆波兰表示法)的解析和计算。 以下是一个简单的Python代码示例,用于解析和计算后缀表达式(也称为逆波兰表示法): def calculate(stack): while True: token = ...
与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。 与前缀或后缀记法不同的是,中缀记法中括号是必需的。计算过程中...
算术表达式的解析器和树表示 该程序将递归树表示形式用于算术表达式(复合模式)。 它从输入中读取此类表达式并对其进行解析,就像编程语言的编译器一样。 解析器通过递归下降操作。 一旦存储在树表示中,就可以对...
也就是我们最常用的算术表达式,中缀表达式对于人类来说比较容易理解,但是不易于计算机解析。 逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学...
•开发了递归下降解析器,将数学表达式转换为树表示形式(中缀,前缀和后缀) •编码逻辑以遍历树的中缀表示并使用堆栈评估表达式 •实现了访问者模式,以将树转换为前缀/后缀中的链表,并评估了表达式的值
2.5.2 算术表达式和运算符的优先级与结合性 2.5.3 表达式中各类数值型数据间的混合运算 2.5.4 自增和自减运算符 2.5.5 强制类型转换运算符 2.6 赋值运算符与赋值表达式 2.6.1 赋值运算符 2.6.2 赋值过程中的类型转换...
2.5.2 算术表达式和运算符的优先级与结合性 2.5.3 表达式中各类数值型数据间的混合运算 2.5.4 自增和自减运算符 2.5.5 强制类型转换运算符 2.6 赋值运算符与赋值表达式 2.6.1 赋值运算符 2.6.2 赋值过程中的类型转换...
在表达式中使用算术运算符时要注意以下几点: 1,运算符两边的运算数字必须是数字 2,使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接,如:${3 + "5"},结果是:35 使用内建的int函数可...
4.6.3伪静态后缀 41 4.6.4URL模式处理 41 4.6.5生成路由地址 42 4.7Ajax返回 42 4.8 重定向和页面跳转 43 4.8.1重定向 43 4.8.2页面跳转 44 4.9HTTP请求方法 46 4.读取输入 48 4.11空操作 50 4.12空控制器...
算术和字符 以下只有一种运算符是有关字符的: $a + $b :加 $a - $b :减 $a * $b :乘 $a / $b :除 $a % $b :取模(余数) $a . $b :字符串连接 逻辑和比较 逻辑运算符有: $a || $b :或 $a or $...