用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>& ... 
随机推荐
- js中的递归总结
			主要从"变量+函数"和"函数+变量"两个方面说明解释. function fun() { // 自己调用自己,称为递归调用 fun(); console.log ... 
- java 反射(Reflection)
			看了很多关于java 反射的文章,自己把所看到的总结一下.对自己,对他人或多或少有帮助吧. Java Reflection是什么? 首先来看看官方文档Oracle里面对Reflection的描述: R ... 
- 用ECMAScript4 ( ActionScript3)  实现Unity的热更新 -- 在脚本中使用MonoBehaviour
			继上次分析了热更新的Demo后,这次来介绍如何在热更新代码中使用MonoBehaviour. MonoBehaviour挂载到GameObject对象上的脚本的基类.平常Unity开发时,简单的做法就 ... 
- ML笔记:Classification: Probabilistic Generative Model
			用回归来做分类: 远大于1的点对于回归来说就是个error, 为了让这些点更接近1,会得到紫色线. 可见,回归中定义模型好坏的方式不适用于分类中.---回归会惩罚那些太过正确的点 如何计算未出现在训练 ... 
- python文件结构与import用法
			首先上一张总结图: 在pycharm中,一般不会将当前文件目录自动加入自己的sourse_path.如果遇到无法import同级目录下的其他模块, 右键make_directory as-->S ... 
- [Tjoi 2013]松鼠聚会
			3170: [Tjoi 2013]松鼠聚会 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1318 Solved: 664[Submit][Stat ... 
- 51nod 1673 树有几多愁
			lyk有一棵树,它想给这棵树重标号. 重标号后,这棵树的所有叶子节点的值为它到根的路径上的编号最小的点的编号. 这棵树的烦恼值为所有叶子节点的值的乘积. lyk想让这棵树的烦恼值最大,你只需输出最大烦 ... 
- 【Uva 11604 编码都有歧义了】
			·你的目的就是要让编码有歧义,这就美妙了. ·英文题,述大意: 给出n个模板字符串,询问是否存在一个字符串,使得用模板串(随便你用多少个)来拼凑这个串,能够至少有两种拼法.如果有,就输出“ ... 
- bzoj1094[ZJOI2007]粒子运动 计算几何
			1094: [ZJOI2007]粒子运动 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 658 Solved: 164[Submit][Status ... 
- STM32 基于定时器的PWM发生器
			脉冲宽度调制(PWM),是英文"Pulse Width Modulation" 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术.简单一点,就 ... 
