用Python最原始的函数模拟eval函数的浮点数运算功能(2)
这应该是我编程以来完成的难度最大的一个函数了.因为可能存在的情况非常多,需要设计合理的参数来控制解析流程.经验概要:
1.大胆假设一些子功能能够实现,看能否建立整个框架.如果在假设的基础上都无法建立,那么必定需要更换思路.
2.穷举所有可能的情况,想清楚该怎么应对它们.如果写完了整个程序才发现,啊!漏了一种情形.代价或许就是全部推翻重写.
我算是能够理解为什么教科书上推荐设计程序之前要先画流程图,那实际是要求你编程之前要先知道解法,并以一种直观的形式保存.这样,你在将解法翻译为代码时,思路才清晰,效率才高.
什么?难道有人会在不知道解法的情况下就编程?显然有这种人的,我这次就是这样干的.
我这次是吃够了亏.因为我没画流程图.虽然最终完成了,脑海中也终于建立了清晰的逻辑,但这浪费了太多的时间了,这要是在工作中,恐怕不太可能有这么宽松的时间限制.
因此,教训是:复杂问题,想好了解法再编程
---------------------------------------------------------------
前几天写了个解析浮点数运算表达式的ieval函数,后来发现不行,因为还要支持负数啊!
其实负数倒没什么,关键是要支持一元运算符了.比如以下写法都是合法的:
-2
--2
---+++2
-+-+-+2
而且要能够体现一元运算符的优先级仅次于幂运算符这个概念.即:
-2**2 应为-4
(-2)**2 应为4
2--2 应为4
而这个一元运算符刚好和二元运算符的+-是完全相同的字符,只能从它出现的位置来判断是一元还是二元.
总之,感觉是要全部推翻重来了.
果然如此.我整整连续搞到今天才把这个东西写出来.为什么会要这么长的时间呢?原因如下:
- 1.我不知道该用哪些参数来控制函数才好.
我一开始是延续之前的思路,主要用操作符opf进行控制.但写着写着发现它让我思路混乱,我都搞不清自己逻辑是否严密了.
好不容易写出个感觉对的版本,一测发现有些情况会出错.而我根本不想去分析是哪个环节出错.因为太混乱了.
- 2.写了太多的注释.
这让程序变得更加庞大,不好去观察逻辑结构.拉来拉去看的比较痛苦.感觉没必要太早地写注释.
- 3.编程之前没有尝试穷举所有可能的情形,只有在出错或者不知不觉发现还存在其他情形的时候才更加靠近正确解法.
那我怎么又慢慢搞出来了呢?
- 1.大概是"只要功夫深铁杵磨成针",我虽然一开始不知道该用哪些参数来控制才合适,但我却知道哪些参数让我感到混乱和痛苦.我也知道哪些参数实际上没有发挥任何作用,可以删掉.而剩余的参数.就那么几个,试来试去,总会抓到对的.
- 2.在一次又一次的出错中我渐渐形成一个完整的表达式概念:
1)表达式的实质是"值"和"运算符"的集合..好像是废话,但现在感觉很深刻
2)一个表达式的开头字符只可能是:-,+,左括号和浮点数字符
3)在左括号或数字尚未出现之前,-和+必定是一元运算符
4)如果一个完整的数字A之后还存在字符,那么必定是二元运算符opf,而且必定至少跟着另外一个完整的数字B.
5)由于运算符有优先级之分,在没有括号界定的情况下,你解析出了opf和B,也不能立即和A进行二元运算,因为你还需解析B之后的字符情况.
比如1+2*3,A是1,opf是+,B是2,你不能先算1+2,因为*优先级高于+.
而假设出现第三个数字C,你也不能断定它能和B进行运算,因为C之后的字符情况也需要检查.
比如一度让我头疼的一种情况:1+2**2**2**2**2**2
- 3.在遇到"我怎么知道什么时候能够安全地返回B的值呢?"这种情形时,我的直觉认为应该采用递归来解决.以前在理解遍历未知深度的文件夹的原理时,建立了一种"无法确定究竟有多深的时候就用递归"的思考习惯.
结果证明我的直觉是正确的.对于优先级,就是应该用递归来检查.
- 4.先假设存在一些函数来实现我想要的功能,尽量把程序的框架建立起来.比如我做了以下假设:
1)提取括号内的表达式,再计算出值的函数.(wipe_brace)
2)安全返回第二个值和剩余字符串的函数.比如1+2*3+4,安全返回6和剩余字符串+4.(get_safeSec)
3)提取完整值的函数.比如1234.5*2,当检测到1时,就继续检测它后面是不是也是数字,直到碰到*,得出1234.5.(get_Longest_number)
这样做的好处是,你会更快地在形式上完成整个程序.而一旦真的实现了那些假设的函数,那么整个程序就相当于完成了.
当然,这种做法有风险,因为万一那些函数不可能实现,那么建立在它之上的程序也无法实现.
不过,当是在探索一个问题的解法时,我觉得这种做法很合理.
好了,现在开始上源码,作为难度里程碑:
# -*- coding:utf-8 -*- def ieval(s):
"模拟eval函数的浮点数运算"
#不进行正运算,不进行多余的负运算.
#例如'+2-1'视为'2-1'.'---2-1'视为'-2-1'.
binaryDic={'+':float.__add__,
'-':float.__sub__,
'*':float.__mul__,
'/':float.__truediv__,
'%':float.__mod__,
'@':float.__pow__,}#为简化判断,将幂运算符'**'替换为'@'
unaryDic={'-':float.__neg__,
'+':float.__pos__,}
priorDic=dict((('@',3),('*',2),('/',2),('%',2),('+',1),('-',1))) def check(s):
'检测非法字符,删除空格,替换幂操作符'
A=set(s)
B=set('0123456789.+-*/%() ')
assert A<=B,'表达式"%s"包含非法字符:%s.'%\
(s,','.join(map(repr,A-B)))
return s.replace(' ','').replace('**','@')
def get_val(fir='',opf='',sec=''):
'智能求值函数.能进行一元,二元或纯值运算'
if opf:
if sec:
return str(binaryDic[opf](float(fir),float(sec)))
return str(unaryDic[opf](float(fir)))
return fir
def is_float(s):
'判断是否浮点数字符'
return s.isdigit() or s=='.'
def is_prior(firOpf,secOpf):
'判断运算符的优先级'
if secOpf=='@':
return False
return priorDic[firOpf]>=priorDic[secOpf]
def wipe_brace(s,start=0):
's是以左括号开头的字符串'
'此函数将消去括号,求出括号内的值,并返回剩余字符'
pos=s.find(')',start)
if s[1:pos].count('(')==s[1:pos].count(')'):
return s[pos+1:] ,in_eval(s[1:pos])
return wipe_brace(s,pos+1)
def get_Longest_number(s,num=''):
's是以左括号或数字开头的字符串'
'返回s首位开始的最长连续数字串和剩余字符串'
if not s :
return s,num
if s[0]=='(':
return wipe_brace(s)
if is_float(s[0]):
return get_Longest_number(s[1:],num+s[0])
return s,num
def get_safeSec(s,cmpOpf='@',neg='',safeSec='',):
'比较s的第一个二元运算符和cmpOpf的优先级'
'以便获得安全的第二个值'
if not s:
return s,get_val(safeSec,neg)
if not safeSec:
if s[0]=='+':
return get_safeSec(s[1:],cmpOpf,neg)
if s[0]=='-':
return get_safeSec(s[1:],cmpOpf,'' if neg=='-' else '-')
if is_float(s[0]) or s[0]=='(':
rest,safeSec=get_Longest_number(s)
return get_safeSec(rest,cmpOpf,neg,safeSec)
if is_prior(cmpOpf,s[0]):
return s,get_val(safeSec,neg)
rest,safeNum=get_safeSec(s[1:],s[0])
return get_safeSec(rest,cmpOpf,neg,get_val(safeSec,s[0],safeNum)) def in_eval(s='',fir='',opf='',sec=''):
'控制整个解析流程'
if not s:
return get_val(fir,opf,sec)
if not fir:
if s[0]=='-':
return in_eval(s[1:],fir,'' if opf=='-' else '-')
if s[0]=='+':
return in_eval(s[1:],fir,opf)
if is_float(s[0]) or s[0]=='(':
return in_eval(*get_Longest_number(s),opf=opf)
if not sec:
if not opf:
rest,sec=get_safeSec(s[1:],s[0])
return in_eval(rest,fir,s[0],sec)
if s[0]!='@':
return in_eval(s,get_val(fir,opf))
rest,sec=get_safeSec(s[1:])
return in_eval(rest,get_val(fir,'@',sec),opf)
return in_eval(s,get_val(fir,opf,sec))
return in_eval(check(s)) if __name__=='__main__':
test=['1+2*3*4*5+20',
'1+2*3',
'1.1+20.02+300.003',
'-2**2',
'(-2)**2',
'1+2*3**-2**(2-3)*2',
'-2**-2**-2**-2',
'(1+2)*(2+3)/(1-3)',
'((9+3)/2)',
'((1234)-1)',
'1-3*--+++-2**--+++((-2))**--+++-(1-2)**--+++-2*2+1*2**6%9',
'((1+3*(-2)**2)*((2%3+((3+3)*2**3+1))*(1-3)*(1+2)+5*6)*4)']
for x in test:
print('___ '.join([str(s).ljust(20) for s in (ieval(x),eval(x))]))
结果:
>>>
141.0 ___ 141
7.0 ___ 7
321.123 ___ 321.123
-4.0 ___ -4
4.0 ___ 4
3.309401076758503 ___ 3.309401076758503
-0.5582965649524321 ___ -0.5582965649524321
-7.5 ___ -7.5
6.0 ___ 6.0
1233.0 ___ 1233
6.242640687119286 ___ 6.242640687119286
-14352.0 ___ -14352
用Python最原始的函数模拟eval函数的浮点数运算功能(2)的更多相关文章
- 用Python最原始的函数模拟eval函数的浮点数运算功能
前几天看一个网友提问,如何计算'1+1'这种字符串的值,不能用eval函数. 我仿佛记得以前新手时,对这个问题完全不知道如何下手. 我觉得处理括号实在是太复杂了,多层嵌套括号怎么解析呢?一些多余的括号 ...
- python中的exec()函数和eval()函数
exec()函数 exec函数用于执行存储在字符串中的python语句 >>> exec("x=1") >>> x 但有时候,直接这样执行可能会 ...
- 编写函数模拟strcpy()函数功能
strcpy(字符数组1,字符串2) strcpy( )用于将字符串2复制到字符数组1中 /* strcpy(字符数组1,字符串2) strcpy( )用于将字符串2复制到字符数组1中 模拟strcp ...
- R语言parse函数与eval函数的字符串转命令行及执行操作
parse()函数能将字符串转换为表达式expression:eval()函数能对表达式求解 A <- : B <- 'print(A)' class(B) C <- parse(t ...
- Python使用map,reduce高阶函数模拟实现Spark的reduceByKey算子功能
# 使用默认的高阶函数map和reduce import randomdef map_function(arg): # 生成测试数据 return (arg,1) list_map = list(m ...
- python 内置函数input/eval(22)
python的内置函数其实挺多的,其中input和eval算得上比较特殊,input属于交互式内置函数,eval函数能直接执行字符串表达式并返回表达式的值. 一.input函数 input是Pytho ...
- [Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用
eval函数不仅仅是一个函数.大多数函数只访问定义它们所在的作用域,而不能访问除此之外的作用域(词法作用域).eval函数具有访问调用它时的整个作用域的能力.编译器编写者首次设法优化js时,eval函 ...
- Matlab匿名函数,子函数,私有函数,重载函数,eval和feval函数
匿名函数,子函数,私有函数等函数类型 匿名函数: 匿名函数没有函数名,也不是.m文件,只包含一个表达式和输入输出参数. Fxy=@(x,y)x.^y+3*x*y x,y为输入输入参数,Fxy为函数名 ...
- python笔记-调用eval函数出现invalid syntax错误
本来是想打算使用eval函数对变量进行赋值的,没想到出现了invalid syntax错误.源代码如下 In [2]: eval('a = 1') File "<string>& ...
随机推荐
- Thread源码剖析
前言 昨天已经写了: 多线程三分钟就可以入个门了! 如果没看的同学建议先去阅读一遍哦~ 在写文章之前通读了一遍<Java 核心技术 卷一>的并发章节和<Java并发编程实战>前 ...
- 实验吧_who are you?(盲注)
who are you? 翻翻源码,抓抓包,乱试一通都没有什么结果 题目中提示有ip,立马应该联想到X-Forwarded-For 虽然知道是这个方面的题,但完全不知道从何入手,悄咪咪去翻一下wp 才 ...
- Topcoder口胡记 SRM 562 Div 1 ~ SRM 599 Div 1
据说做TC题有助于提高知识水平? :) 传送门:https://284914869.github.io/AEoj/index.html 转载请注明链接:http://www.cnblogs.com/B ...
- 【HDU 1576】 A/B
Problem Description 要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1). Input 数据的 ...
- poj2331 (IDA*)
题意:给你k种管道,然后是每种的长度,每种的数量,求(x1,y1)到(x2,y2)所用管道的最少数量 思路: 最开始考虑的是直接bfs,但是没有成功. 然后发现可以先找x轴x1 到 x2 ,再找y轴y ...
- [2017/5/28]FJ四校联考
来自FallDream的博客,未经允许,请勿转载,谢谢. 话说这一段时间算是过去了,好久好久之后终于又有联考了 没想到这次到我们学校出题,昨天才想起来,临时花一天赶了一套,我出了一个sbFFT,质量 ...
- bzoj3622已经没有什么好害怕的了 dp+组合+容斥(?)
3622: 已经没有什么好害怕的了 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 1033 Solved: 480[Submit][Status][ ...
- java 之 MyBatis(sql 可以执行,在eclipse执行报错问题)
前段时间写 mybatis Sql 查询语句的时候,发现一个很奇怪的现象,我写的SQL 语句在 pl/Sql 中明明可以执行,但是放到 eclipse 中执行却报错,因为时间比较久,依稀记得是文字与字 ...
- 常用SQL Server命令(持续) | Commonly used SQL Server command list (Cont')
---------------------------------------------------- 1. 查看某数据库中某表详细信息 SP_HELP USE DB_NAME GO SP_HELP ...
- Linux命令-关机命令详解
关机命令:1.halt 立刻关机 2.poweroff 立刻关机 3.shutdown -h now 立刻关机(root用户使用) 4.shutdown -h 10 10分钟后自动关机 如果是通过sh ...