zoukankan      html  css  js  c++  java
  • 用Python3实现表达式求值

    一、题目描述

      请用 python3 编写一个计算器的控制台程序,支持加减乘除、乘方、括号、小数点,运算符优先级为括号>乘方>乘除>加减,同级别运算按照从左向右的顺序计算。

    二、输入描述

    1. 数字包括"0123456789",小数点为".",运算符包括:加("+")、减("-")、乘("*")、除("/")、乘方("^",注:不是**!)、括号("()")
    2. 需要从命令行参数读入输入,例如提交文件为 main.py,可以用 python3 main.py "1+2-3+4" 的方式进行调用
    3. 输入需要支持空格,即 python3 main.py "1     +     2      -     3    +    4" 也需要程序能够正确给出结果
    4. 所有测试用例中参与运算的非零运算数的绝对值范围保证在 10^9-10^(-10) 之内, 应该输出运算结果时非零运算结果绝对值也保证在该范围内

    三、输出描述

    1. 数字需要支持小数点,输出结果取10位有效数字,有效数字位数不足时不能补0
    2. 对于不在输入描述内的输入,输出INPUT ERROR
    3. 对于格式不合法(例如括号不匹配等)的输入,输出 FORMAT ERROR
    4. 对于不符合运算符接收的参数范围(例如除0等)的输入,输出VALUE ERROR
    5. 对于2、3、4的情况,输出即可,不能抛出异常
    6. 同时满足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 这种方便的脚本语言呢~

    大致代码区区几行:

    1 from sys import argv
    2 
    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 # @Email:            maples7@163.com
      8 # @Function:
      9 #                   
     10 #       A Python Calculator for Operator +-*/()^
     11 #
     12 #################################
     13 
     14 from sys import argv
     15 from decimal import *
     16 
     17 def delBlank(str):
     18     """
     19     Delete all blanks in the str
     20     """
     21     ans = ""
     22     for e in str:
     23         if e != " ":
     24             ans += e
     25     return ans
     26 
     27 def precede(a, b):
     28     """
     29     Compare the prior of operator a and b
     30     """
     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     return prior[char2num[a]][char2num[b]]
     57 
     58 def operate(a, b, operator):
     59     """
     60     Operate [a operator b]
     61     """
     62     if operator == '+':
     63         ans = a + b
     64     elif operator == '-':
     65         ans = a - b
     66     elif operator == '*':
     67         ans = a * b
     68     elif operator == '/':
     69         if b == 0:
     70             ans = "VALUE ERROR"
     71         else:
     72             ans = a / b
     73     elif operator == '^':
     74         if a == 0 and b == 0:
     75             ans = "VALUE ERROR"
     76         else:
     77             ans = a ** b
     78 
     79     return ans
     80 
     81 def calc(exp):
     82     """
     83     Calculate the ans of exp
     84     """
     85     exp += '#'
     86     operSet = "+-*/^()#"
     87     stackOfOperator, stackOfNum = ['#'], []
     88     pos, ans, index, length = 0, 0, 0, len(exp)
     89     while index < length:
     90         e = exp[index]
     91         if e in operSet:
     92             # calc 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                     return ans
    104                 else:
    105                     stackOfNum.append(ans)
    106             elif compare == '<':
    107                 stackOfOperator.append(topOperator)
    108                 stackOfOperator.append(e)
    109                 index += 1
    110             elif compare == '=':
    111                 index += 1
    112             elif compare == ' ':
    113                 return "FORMAT ERROR"
    114         else:
    115             # get the next num
    116             pos = index
    117             while not exp[index] in operSet:
    118                 index += 1
    119             temp = exp[pos:index]
    120 
    121             # delete all 0 of float in the end
    122             last = index - 1
    123             if '.' in temp:
    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         return stackOfNum.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 这样的情况。

  • 相关阅读:
    吴裕雄--天生自然C++语言学习笔记:C++ 标准库
    吴裕雄--天生自然C++语言学习笔记:C++ STL 教程
    吴裕雄--天生自然C++语言学习笔记:C++ Web 编程
    吴裕雄--天生自然C++语言学习笔记:C++ 多线程
    吴裕雄--天生自然C++语言学习笔记:C++ 信号处理
    吴裕雄--天生自然C++语言学习笔记:C++ 预处理器
    吴裕雄--天生自然C++语言学习笔记:C++ 模板
    吴裕雄--天生自然C++语言学习笔记:C++ 命名空间
    ZOJ1905Power Strings (KMP||后缀数组+RMQ求循环节)
    POJ3693Maximum repetition substring (循环节)(后缀数组+RMQ)
  • 原文地址:https://www.cnblogs.com/maples7/p/5212744.html
Copyright © 2011-2022 走看看