一、题目描述

请用 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 实现,初看一下,首先想到的其实是一种“讨巧”(作弊 >_

大致代码区区几行:

1 from sys importargv2
3 if __name__ == "__main__":4 exp = argv[1]5 print(eval(exp))

即便是考虑到题干中的输出要求做异常处理(try...except)输出相应的错误信息,也就十行左右。

但稍深入就会发现,有些情形还是无法按要求处理的,比如样例 “1 + + 2”,用 eval() 会输出结果 “3” (+2 前的 “+” 被当作正号),而题目要求输出 “FORMAT ERROR”。

没办法,只能老老实实做苦力活儿了。

表达式求值其实是《数据结构》课程里一个基本且重要的问题之一,一般作为 “栈” 的应用来提出。

问题的关键就是需要按照人们通常理解的运算符的优先级来进行计算,而在计算过程中的临时结果则用 栈 来存储。

为此,我们可以首先构造一个 “表” 来存储当不同的运算符 “相遇” 时,它们谁更 “屌” 一些(优先级更高一些)。这样就可以告诉计算机,面对不同的情形,它接下来应该如何来处理。

其次,我们需要构造两个栈,一个运算符栈,一个运算数栈。

运算符栈是为了搞定当某个运算符优先级较低时,暂时先让它呆在栈的底部位置,待它可以 “重见天日” 的那一天(优先级相对较高时),再把它拿出来使用。正确计算完成后,此栈应为空。

运算数栈则是为了按合理的计算顺序存储运算中间结果。正确计算完成后,此栈应只剩下一个数,即为最后的结果。

完整的代码如下:

1 #-*- coding: utf-8 -*-
2
3 #################################
4 #@Author: Maples7
5 #@LaunchTime: 2016/2/24 12:32:38
6 #@FileName: main
7 #8 #@Function:
9 #
10 #A Python Calculator for Operator +-*/()^
11 #12 #################################
13
14 from sys importargv15 from decimal import *
16
17 defdelBlank(str):18 """
19 Delete all blanks in the str20 """
21 ans = ""
22 for e instr:23 if e != " ":24 ans +=e25 returnans26
27 defprecede(a, b):28 """
29 Compare the prior of operator a and b30 """
31 #the prior of operator
32 prior =(33 #‘+‘ ‘-‘ ‘*‘ ‘/‘ ‘(‘ ‘)‘ ‘^‘ ‘#‘
34 (‘>‘, ‘>‘, ‘‘, ‘‘), #‘+‘
35 (‘>‘, ‘>‘, ‘‘, ‘‘), #‘-‘
36 (‘>‘, ‘>‘, ‘>‘, ‘>‘, ‘‘, ‘‘), #‘*‘
37 (‘>‘, ‘>‘, ‘>‘, ‘>‘, ‘‘, ‘‘), #‘/‘
38 (‘
39 (‘>‘, ‘>‘, ‘>‘, ‘>‘, ‘ ‘, ‘>‘, ‘>‘, ‘>‘), #‘)‘
40 (‘>‘, ‘>‘, ‘>‘, ‘>‘, ‘‘, ‘>‘, ‘>‘), #‘^‘
41 (‘
42 )43
44 #operator to index of prior[8][8]
45 char2num ={46 ‘+‘: 0,47 ‘-‘: 1,48 ‘*‘: 2,49 ‘/‘: 3,50 ‘(‘: 4,51 ‘)‘: 5,52 ‘^‘: 6,53 ‘#‘: 7
54 }55
56 returnprior[char2num[a]][char2num[b]]57
58 defoperate(a, b, operator):59 """
60 Operate [a operator b]61 """
62 if operator == ‘+‘:63 ans = a +b64 elif operator == ‘-‘:65 ans = a -b66 elif operator == ‘*‘:67 ans = a *b68 elif operator == ‘/‘:69 if b ==0:70 ans = "VALUE ERROR"
71 else:72 ans = a /b73 elif operator == ‘^‘:74 if a == 0 and b ==0:75 ans = "VALUE ERROR"
76 else:77 ans = a **b78
79 returnans80
81 defcalc(exp):82 """
83 Calculate the ans of exp84 """
85 exp += ‘#‘
86 operSet = "+-*/^()#"
87 stackOfOperator, stackOfNum = [‘#‘], []88 pos, ans, index, length =0, 0, 0, len(exp)89 while index <90 e="exp[index]91" if inoperset:92 according to the prior>
93 topOperator =stackOfOperator.pop()94 compare =precede(topOperator, e)95 if compare == ‘>‘:96 try:97 b =stackOfNum.pop()98 a =stackOfNum.pop()99 except:100 return "FORMAT ERROR"
101 ans =operate(a, b, topOperator)102 if ans == "VALUE ERROR":103 returnans104 else:105 stackOfNum.append(ans)106 elif compare == ‘
110 elif compare == ‘=‘:111 index += 1
112 elif compare == ‘ ‘:113 return "FORMAT ERROR"
114 else:115 #get the next num
116 pos =index117 while not exp[index] inoperSet:118 index += 1
119 temp =exp[pos:index]120
121 #delete all 0 of float in the end
122 last = index - 1
123 if ‘.‘ intemp:124 while exp[last] == ‘0‘:125 last -= 1
126 temp = exp[pos:last + 1]127
128 try:129 temp =Decimal(temp)130 except:131 return "INPUT ERROR"
132 stackOfNum.append(temp)133
134 if len(stackOfNum) == 1 and stackOfOperator ==[]:135 returnstackOfNum.pop()136 else:137 return "INPUT ERROR"
138
139 if __name__ == "__main__":140 #get the exp
141 exp = argv[1]142
143 #set the precision
144 getcontext().prec = 10
145
146 #delete blanks
147 exp =delBlank(exp)148
149 #calc and print the ans
150 ans =calc(exp)151 print(ans)

其中需要稍微注意的细节有:

1. 表达式处理前,前后都插入一个 ‘#‘ 作为一个特殊的运算符,这样做是为了方便统一处理,即不用再去特别判断表达式是否已经结束(从而引发一系列边界问题导致代码冗长复杂,这种处理也可称之为 “哨兵” 技巧)。如果最后两个运算符相遇则说明表达式处理完毕,这个运算符的优先级也是最低的(在 prior 表中也有体现)。

2. 输出要求比较复杂,抛去错误信息输出不说(只能具体情况具体分析),不能输出多余的0,折腾了一会儿最后发现用高精度的 Decimal 可以完美满足题目要求。

3. 由于不能输出多余的0,所以在带有小数部分的数字 “录入” 时(代码115-132行),就要把一些多余的0提前去掉(代码121-126行),比如 2.0 这样的情况。