用Python3实现表达式求值
一、题目描述
请用 python3 编写一个计算器的控制台程序,支持加减乘除、乘方、括号、小数点,运算符优先级为括号>乘方>乘除>加减,同级别运算按照从左向右的顺序计算。
二、输入描述
- 数字包括"0123456789",小数点为".",运算符包括:加("+")、减("-")、乘("*")、除("/")、乘方("^",注:不是**!)、括号("()")
- 需要从命令行参数读入输入,例如提交文件为 main.py,可以用 python3 main.py "1+2-3+4" 的方式进行调用
- 输入需要支持空格,即 python3 main.py "1 + 2 - 3 + 4" 也需要程序能够正确给出结果
- 所有测试用例中参与运算的非零运算数的绝对值范围保证在 10^9-10^(-10) 之内, 应该输出运算结果时非零运算结果绝对值也保证在该范围内
三、输出描述
- 数字需要支持小数点,输出结果取10位有效数字,有效数字位数不足时不能补0
- 对于不在输入描述内的输入,输出INPUT ERROR
- 对于格式不合法(例如括号不匹配等)的输入,输出 FORMAT ERROR
- 对于不符合运算符接收的参数范围(例如除0等)的输入,输出VALUE ERROR
- 对于2、3、4的情况,输出即可,不能抛出异常
- 同时满足2、3、4中多个条件时,以序号小的为准
四、样例
输入: 1 + 2 - 3 + 4
输出: 4
输入: 1 + 2 - 3 + 1 / 3
输出: 0.3333333333
输入: 1 + + 2
输出: FORMAT ERROR
输入: 1 / 0
输出: VALUE ERROR
输入: a + 1
输出: INPUT ERROR
【注:此题为TsinghuaX:34100325X 《软件工程》 MOOC 课程 Spring, 2016 Chapter 1 Problem,此文发布时,这门课刚刚在 “学堂在线” 上开课两天】
用 Python3 实现,初看一下,首先想到的其实是一种“讨巧”(作弊 >_<)的方法(由于曾经网站被挂码的悲壮历史……),即通过 eval() 函数(这应该是黑客用得最多的一个函数了吧+_+)直接求解表达式,谁叫题目指定用 Python 这种方便的脚本语言呢~
大致代码区区几行:
from sys import argv if __name__ == "__main__":
exp = argv[1]
print(eval(exp))
即便是考虑到题干中的输出要求做异常处理(try...except)输出相应的错误信息,也就十行左右。
但稍深入就会发现,有些情形还是无法按要求处理的,比如样例 “1 + + 2”,用 eval() 会输出结果 “3” (+2 前的 “+” 被当作正号),而题目要求输出 “FORMAT ERROR”。
没办法,只能老老实实做苦力活儿了。
表达式求值其实是《数据结构》课程里一个基本且重要的问题之一,一般作为 “栈” 的应用来提出。
问题的关键就是需要按照人们通常理解的运算符的优先级来进行计算,而在计算过程中的临时结果则用 栈 来存储。
为此,我们可以首先构造一个 “表” 来存储当不同的运算符 “相遇” 时,它们谁更 “屌” 一些(优先级更高一些)。这样就可以告诉计算机,面对不同的情形,它接下来应该如何来处理。
其次,我们需要构造两个栈,一个运算符栈,一个运算数栈。
运算符栈是为了搞定当某个运算符优先级较低时,暂时先让它呆在栈的底部位置,待它可以 “重见天日” 的那一天(优先级相对较高时),再把它拿出来使用。正确计算完成后,此栈应为空。
运算数栈则是为了按合理的计算顺序存储运算中间结果。正确计算完成后,此栈应只剩下一个数,即为最后的结果。
完整的代码如下:
# -*- coding: utf-8 -*- #################################
# @Author: Maples7
# @LaunchTime: 2016/2/24 12:32:38
# @FileName: main
# @Email: maples7@163.com
# @Function:
#
# A Python Calculator for Operator +-*/()^
#
################################# from sys import argv
from decimal import * def delBlank(str):
"""
Delete all blanks in the str
"""
ans = ""
for e in str:
if e != " ":
ans += e
return ans def precede(a, b):
"""
Compare the prior of operator a and b
"""
# the prior of operator
prior = (
# '+' '-' '*' '/' '(' ')' '^' '#'
('>', '>', '<', '<', '<', '>', '<', '>'), # '+'
('>', '>', '<', '<', '<', '>', '<', '>'), # '-'
('>', '>', '>', '>', '<', '>', '<', '>'), # '*'
('>', '>', '>', '>', '<', '>', '<', '>'), # '/'
('<', '<', '<', '<', '<', '=', '<', ' '), # '('
('>', '>', '>', '>', ' ', '>', '>', '>'), # ')'
('>', '>', '>', '>', '<', '>', '>', '>'), # '^'
('<', '<', '<', '<', '<', ' ', '<', '=') # '#'
) # operator to index of prior[8][8]
char2num = {
'+': 0,
'-': 1,
'*': 2,
'/': 3,
'(': 4,
')': 5,
'^': 6,
'#': 7
} return prior[char2num[a]][char2num[b]] def operate(a, b, operator):
"""
Operate [a operator b]
"""
if operator == '+':
ans = a + b
elif operator == '-':
ans = a - b
elif operator == '*':
ans = a * b
elif operator == '/':
if b == 0:
ans = "VALUE ERROR"
else:
ans = a / b
elif operator == '^':
if a == 0 and b == 0:
ans = "VALUE ERROR"
else:
ans = a ** b return ans def calc(exp):
"""
Calculate the ans of exp
"""
exp += '#'
operSet = "+-*/^()#"
stackOfOperator, stackOfNum = ['#'], []
pos, ans, index, length = 0, 0, 0, len(exp)
while index < length:
e = exp[index]
if e in operSet:
# calc according to the prior
topOperator = stackOfOperator.pop()
compare = precede(topOperator, e)
if compare == '>':
try:
b = stackOfNum.pop()
a = stackOfNum.pop()
except:
return "FORMAT ERROR"
ans = operate(a, b, topOperator)
if ans == "VALUE ERROR":
return ans
else:
stackOfNum.append(ans)
elif compare == '<':
stackOfOperator.append(topOperator)
stackOfOperator.append(e)
index += 1
elif compare == '=':
index += 1
elif compare == ' ':
return "FORMAT ERROR"
else:
# get the next num
pos = index
while not exp[index] in operSet:
index += 1
temp = exp[pos:index] # delete all 0 of float in the end
last = index - 1
if '.' in temp:
while exp[last] == '':
last -= 1
temp = exp[pos:last + 1] try:
temp = Decimal(temp)
except:
return "INPUT ERROR"
stackOfNum.append(temp) if len(stackOfNum) == 1 and stackOfOperator == []:
return stackOfNum.pop()
else:
return "INPUT ERROR" if __name__ == "__main__":
# get the exp
exp = argv[1] # set the precision
getcontext().prec = 10 # delete blanks
exp = delBlank(exp) # calc and print the ans
ans = calc(exp)
print(ans)
其中需要稍微注意的细节有:
1. 表达式处理前,前后都插入一个 '#' 作为一个特殊的运算符,这样做是为了方便统一处理,即不用再去特别判断表达式是否已经结束(从而引发一系列边界问题导致代码冗长复杂,这种处理也可称之为 “哨兵” 技巧)。如果最后两个运算符相遇则说明表达式处理完毕,这个运算符的优先级也是最低的(在 prior 表中也有体现)。
2. 输出要求比较复杂,抛去错误信息输出不说(只能具体情况具体分析),不能输出多余的0,折腾了一会儿最后发现用高精度的 Decimal 可以完美满足题目要求。
3. 由于不能输出多余的0,所以在带有小数部分的数字 “录入” 时(代码115-132行),就要把一些多余的0提前去掉(代码121-126行),比如 2.0 这样的情况。
用Python3实现表达式求值的更多相关文章
- 表达式求值(noip2015等价表达式)
题目大意 给一个含字母a的表达式,求n个选项中表达式跟一开始那个等价的有哪些 做法 模拟一个多项式显然难以实现那么我们高兴的找一些素数代入表达式,再随便找一个素数做模表达式求值优先级表 - ( ) + ...
- 数据结构算法C语言实现(八)--- 3.2栈的应用举例:迷宫求解与表达式求值
一.简介 迷宫求解:类似图的DFS.具体的算法思路可以参考书上的50.51页,不过书上只说了粗略的算法,实现起来还是有很多细节需要注意.大多数只是给了个抽象的名字,甚至参数类型,返回值也没说的很清楚, ...
- nyoj305_表达式求值
表达式求值 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 Dr.Kong设计的机器人卡多掌握了加减法运算以后,最近又学会了一些简单的函数求值,比如,它知道函数min ...
- 利用栈实现算术表达式求值(Java语言描述)
利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...
- 数据结构--栈的应用(表达式求值 nyoj 35)
题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=35 题目: 表达式求值 时间限制:3000 ms | 内存限制:65535 KB描述 AC ...
- NOIP2013普及组 T2 表达式求值
OJ地址:洛谷P1981 CODEVS 3292 正常写法是用栈 #include<iostream> #include<algorithm> #include<cmat ...
- HNU 12817 Shipura(表达式求值)
题目链接:http://acm.hnu.cn/online/?action=problem&type=show&id=12817 解题报告:定义两种运算符号,一种是>>,就 ...
- NOIP201302表达式求值
NOIP201302表达式求值 题目描述 Description 给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值. 输入描述 Input Description 输入仅有一行,为需要你计 ...
- OpenJudge计算概论-简单算术表达式求值
/*===================================== 简单算术表达式求值 总时间限制: 1000ms 内存限制: 65536kB 描述 2位正整数的简单算术运算(只考虑整数运 ...
随机推荐
- 【Yeoman】热部署web前端开发环境
本文来自 “简时空”:<[Yeoman]热部署web前端开发环境>(自动同步导入到博客园) 1.序言 记得去年的暑假看RequireJS的时候,曾少不更事般地惊为前端利器,写了<Sp ...
- 用于阻止div上的事件和div上的按钮的事件同时触发
event.stopPropagation() 阻止事件冒泡 用于ie11以上
- 虚拟机启动linux系统报错,此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态
在使用虚拟机启动linux的时候报错,如下: 已将该虚拟机配置为使用 64 位客户机操作系统.但是,无法执行 64 位操作. 此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态. ...
- Oracle 游标
游标的简介 游标的概念 游标是从数据表中提取出来的数据,以临时表的形式存放在内存中,在游标中有一个数据指针,在初始状态下指向的是首记录,利用fetch语句可以移动该指针,从而对游标中的数据进行各种操作 ...
- scalac 学习
val logEnable = false def log(msg: => String) = if (logEnable) println(msg) val MSG = "progr ...
- OC语言中BOOL 和 bool 区别
1.类型不同 BOOL为int型: bool为布尔型: 2.长度不同 bool只有一个字节: BOOL长度视实际环境来定,一般可认为是4个字节: 3.取值不同 bool取值false和true,是0和 ...
- 键盘对应的ASCII码
ESC键 VK_ESCAPE (27)回车键: VK_RETURN (13)TAB键: VK_TAB (9)Caps Lock键: VK_CAPITAL (20)Shift键: VK_SHIFT ($ ...
- SET ANSI_NULLS ON ……
SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON GO 是什么意思? 语法 SET QUOT ...
- Git 分支
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照,某一次的提交指向这处时刻的文件快照,看起来就像每次提交都保存了当时的文件,连续的提交形成一条长链 分支 指向某一个特定的提交,不同的 ...
- μC/OS-Ⅲ系统中的任务种类及基本状态
在μC/OS-Ⅲ系统中,任务自身一共有五种状态. 1.休眠态 调用函数OSTaskCreate()创建任务后,任务就可以接受μC/OS-Ⅲ的管理.处于休眠态的任务代码实际上已经写入代码空间中了,但是μ ...