这应该是我编程以来完成的难度最大的一个函数了.因为可能存在的情况非常多,需要设计合理的参数来控制解析流程.经验概要:

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)的更多相关文章

  1. 用Python最原始的函数模拟eval函数的浮点数运算功能

    前几天看一个网友提问,如何计算'1+1'这种字符串的值,不能用eval函数. 我仿佛记得以前新手时,对这个问题完全不知道如何下手. 我觉得处理括号实在是太复杂了,多层嵌套括号怎么解析呢?一些多余的括号 ...

  2. python中的exec()函数和eval()函数

    exec()函数 exec函数用于执行存储在字符串中的python语句 >>> exec("x=1") >>> x 但有时候,直接这样执行可能会 ...

  3. 编写函数模拟strcpy()函数功能

    strcpy(字符数组1,字符串2) strcpy( )用于将字符串2复制到字符数组1中 /* strcpy(字符数组1,字符串2) strcpy( )用于将字符串2复制到字符数组1中 模拟strcp ...

  4. R语言parse函数与eval函数的字符串转命令行及执行操作

    parse()函数能将字符串转换为表达式expression:eval()函数能对表达式求解 A <- : B <- 'print(A)' class(B) C <- parse(t ...

  5. Python使用map,reduce高阶函数模拟实现Spark的reduceByKey算子功能

    # 使用默认的高阶函数map和reduce import randomdef map_function(arg):  # 生成测试数据 return (arg,1) list_map = list(m ...

  6. python 内置函数input/eval(22)

    python的内置函数其实挺多的,其中input和eval算得上比较特殊,input属于交互式内置函数,eval函数能直接执行字符串表达式并返回表达式的值. 一.input函数 input是Pytho ...

  7. [Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用

    eval函数不仅仅是一个函数.大多数函数只访问定义它们所在的作用域,而不能访问除此之外的作用域(词法作用域).eval函数具有访问调用它时的整个作用域的能力.编译器编写者首次设法优化js时,eval函 ...

  8. Matlab匿名函数,子函数,私有函数,重载函数,eval和feval函数

    匿名函数,子函数,私有函数等函数类型 匿名函数: 匿名函数没有函数名,也不是.m文件,只包含一个表达式和输入输出参数. Fxy=@(x,y)x.^y+3*x*y x,y为输入输入参数,Fxy为函数名 ...

  9. python笔记-调用eval函数出现invalid syntax错误

    本来是想打算使用eval函数对变量进行赋值的,没想到出现了invalid syntax错误.源代码如下 In [2]: eval('a = 1') File "<string>& ...

随机推荐

  1. CSS3和H5的新特性

    H5的新特性 1.   用于绘画 canvas 元素. 2.   用于媒介回放的 video 和 audio 元素. 3.   本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不 ...

  2. ReactNative Android之原生UI组件动态addView不显示问题解决

    ReactNative Android之原生UI组件动态addView不显示问题解决 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请表明出处:http://www.cnblogs.com ...

  3. pymysql实现从a表过滤出有效信息添加至b表

    # Author: yeshengbao # -- coding: utf-8 -- # @Time : 2018/4/16 19:23 import pymysql # 创建连接 conn = py ...

  4. PHPCMS v9.6.0 wap模块 SQL注入

    调试这个漏洞的时候踩了个坑,影响的版本是php5.4以后. 由于漏洞是由parse_str()函数引起的,但是这个函数在gpc开启的时候(也就是php5.4以下)会对单引号进行过滤\'  . 看这里: ...

  5. NIO-学习

    通道(Channel) 通道表示打开到 IO 设备(例如:文件.套接字)的连接.若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区.然后操作缓冲区,对数据进行处理.C ...

  6. codevs 搜索题汇总(青铜+白银级)

    1792 分解质因数  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 青铜 Bronze   题目描述 Description 编写一个把整数N分解为质因数乘积的程序. 输入描 ...

  7. 【CODEVS 6384 大米兔学全排列】

    ·大米兔学习全排列,还有一些逆序对,还有一棵二叉索引树.· ·分析:       首先肯定不是像题目上说的那样,使用next_permutation去完成这道题,因为就算是线性的它也不能承受庞大的排列 ...

  8. (ubuntu)linux C编程之sleep()和usleep()的使用和区别

    ### 函数名: sleep 头文件: #include <windows.h> // 在VC中使用带上头文件 #include <unistd.h> // 在gcc编译器中, ...

  9. ubuntu14.0464位 Ros环境 安装halcon13.01

    至于ROS的系统,之前就是安装好的,如果有疑问的可以参考官网的安装教程,按照指令一步一步的操作,http://wiki.ros.org/cn/indigo/Installation/Ubuntu (1 ...

  10. Unix系统的文件目录项的内容是什么,这样处理的好处是什么?

    (Unix系统采用树型目录结构,而且目录中带有交叉勾链.每个目录表称为一个目录文件.一个目录文件是由目录项组成的.) 每个目录项包含16个字节,一个辅存磁盘块(512B)包含32个目录项.在目录项中, ...