Python攻克之路-计算器思路
需要实现的计算式
source='1 - 2 * ((60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14)) - (-4*3)/ (16-3*2))'
思路一
第一步:把表达式进行规范化,如表达式中的空格,进行检测
source='1 - 2 * ((60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14)) - (-4*3)/ (16-3*2))' def check(s): ##检测表达式里不规范的的输入 flag=True if re.findall('[a-zA-Z]',s): #判断是否有字母 print('Invalid') flag=False return flag ##先打个标记,返回一个值,false证明检测是有问题 def format(s): ##把空格之类多余的输入进行转换 s=s.replace(' ','') #不需要匹配,对里面所有的空格替换就可以,再用s接收 s=s.replace('++','+') return s ##处理后,返回一个s if check(source): strs=format(source) #检查后,源数据由source变成strs
第二步:设计思路
分析:表达式分为有括号和无括号两种情况
a. 有括号的情况
检测表达式中是否有括号,\(,因为上面检测时,就判断左右括号是一致的,这时只需要使用一边的括号就可以,while的循环是会继续走else
while re.search('\('): pass ##先处理有括号的,从最里面的括号开始 else: pass ##再处理没有括号的
实际
while re.search('\('): strs=re.search('\([^()]+\)',strs).group() #已经匹配到,但是匹配到可能有加减乘除,不会有括号
取最里层括号的过程:这里要取的是(2+4)
In [5]: import re In [6]: res=re.search('\([^()]\)','((2+4)*2)') ##[^()]是不为括号的内容,2+4是三个元素,所以在[^()]后面加下+号 In [7]: print(res) None In [8]: res=re.search('\([^()]+\)','((2+4)*2)') In [9]: print(res) <_sre.SRE_Match object; span=(1, 6), match='(2+4)'> In [10]: print(res.group()) (2+4) ####\([^()]+\)+,\(\)是匹配(),[^()]是匹配2+4中的其中一个内容,[^()]+是2+4 In [11]: res=re.search('\([^()]+\)','(1+1+(2+4)*2)') ##无论(2+4)外加多少内容都可以匹配 In [12]: print(res.group()) (2+4)
b.对括号中运算进行判断,乘除的优先级比较高
假设小括号内的内容为(2+5*3),就要先运算5*3
def cal_mul_div(s): #进行乘除法的运算,所以这里要可以检测到5*3 return s #返回s,(2+5*3)=(2+15) def cal_add_sub(s): #(2+15) #加减的函数 return s #(17) while re.search('\('): strs=re.search('\([^()]+\)',strs) strs=cal_mul_div(strs) #(2+15) strs=cal_add_sub(strs) #(17) 实际17是字符串可以做切片处理,str[1:-1]=17,-1是最后一个不取 else: #else 处理的是没有括号的,所以直接放入乘除,再到加减就可以 strs=cal_mul_div(strs) strs=cal_add_sub(strs)
c.乘除的处理
分析:(1).乘的过程中可能有整数相乘如5*3,还有小数相乘的情况如5.4*3,整数的情况可以是\d*\d最简单的,但是它不确定位数,\d+*\d+可以使用+号重复前面数字的次数,这样位数就可以随机变化
(2).小数的相乘,\d+.\d+*\d+,这种写法存在问题,对于出现整数相乘就不行,所以小数点是可有可无,使用?来实现0次或者1次,应该为d+.?\d+*\d+,但还有问题如就匹配5*3就不能匹配,首先\d+必须要有一个数字,5是匹配上了,点是可以没有,但是后面的\d+,如果使用+号表示一定要有,整数5后面是没有,所以要使用*号(0到无穷)为\d*
(3).乘法为得到两个数字都要一样:\d+\.?\d*\*\d+\.?\d*
(4).乘除同时使用[*/] : \d+\.?\d* [*/] \d+\.?\d*
(5).对得到的字符值使用split进行切分
(6).使用If检测是乘法还是除法,决定下面的操作
(7).直接使用float对字符串进行一个强转换
(8).替换原来的结果replace
正则匹配乘除
def cal_mul_div(s): #(2+5*3) re.search('\d+\.?\d*[*/]\d+\.?\d*',s) return s
使用split切分
def cal_mul_div(s): #(2+5*3) re.search('\d+\.?\d*[*/]\d+\.?\d*',s) #(2+15),得到一个字符串后,要做切分 x,y=re.split('[*/]',ret) #使用*乘号或/除号进行切分,得到字符'5','3'作为元素在一个序列,再使用两个值去接收 return s
切分后对乘法或除法使用if判断
def cal_mul_div(s): #(2+5*3) re.search('\d+\.?\d*[*/]\d+\.?\d*',s) #(2+15),得到一个字符串后,要做切分 x,y=re.split('[*/]',ret) #使用*乘号或/除号进行切分,得到字符'5','3'作为元素在一个序列,再使用两个值去接收 if ..... return s
对浮点型进行操作
def cal_mul_div(s): #(2+0.5*3.8) ret1=re.search('\d+\.?\d*[*/]\d+\.?\d*',s) x,y=re.split('[*/]',ret) #使用*乘号或/除号进行切分,得到字符'0.5','3.8'作为元素在一个序列,再使用两个值去接收 ret2=float(x)*float(y) #得到一个浮点行 return s
要把ret得到结果替换原来的结果#(2+0.5*3.8)
def cal_mul_div(s): #(2+0.5*3.8) ret1=re.search('\d+\.?\d*[*/]\d+\.?\d*',s) x,y=re.split('[*/]',ret) #使用*乘号或/除号进行切分,得到字符'0.5','3.8'作为元素在一个序列,再使用两个值去接收 ret2=float(x)*float(y) #得到一个浮点行 str(ret2) #'1.9' s.replace(ret1,ret2) #把ret1替换成ret2 return s #(2+1.9)
思路二:具体实现
源数据
source='1 - 2 * ((60-30 + (-9-2-5-4*5-6/3-40/8+5*9)) - (-4*3)/ (16-3*2))' #source = "2 - ((-30/3)*(3-2))" #source ="4**8" #source = "4*7*2" 最简单的,没括号的
1. 对表达式进行检查,判断字符的规范
if check_expression(source): print("source: ", source) print("eval result: ", eval(source)) source = format_string(source) print(source)
(1).check_expression函数,检查表达式的合法性
def check_expression(string): check_result = True #括号是否匹配 if not string.count("(") == string(")"): #判断左括号和右括号的数量是否相等 print("表达错误,括号示闭合!") check_result = False if re.findall('[a-z]+', string.lower()): #判断是否有字母,有就显示非法字符 print("表达式错误,包含非法字符!") check_result = False #修改标志位 return check_result #最后返回标记位时,默认是为True,一旦有错误就为false
(2). 表达式没问题,需要对它进行格式化
format_string函数,格式化字符串 def format_string(string): string = string.replace("--", "+") #把这些不规范的进行一个替换 string = string.replace("-+", "-") string = string.replace("++", "+") string = string.replace("+-", "+") string = string.replace("*-", "*") string = string.replace("*+", "*") string = string.replace("/+", "/") string = string.replace(' ', '') return string
2.计算
(7 /3*99/4*2998 +10 * 568/14)把最里层的括号取出后,如果要做乘法,可能不只一个乘法,有可能是连着相乘,需要做循环处理,如while时,应该判断检测是不是有符合条件的表达式,是一个数乘以一个数,或者一个数除以一个数,如果检测出来就应该一直做乘除法的计算,直接计算只剩加减法,
while source.count("(") > 0: #在最外层,首先判断是否有括号,在下面进行处理 strs = re.search('\([^()]*\)', source).group() #取最里层括号的内容,然后进行加减乘除运算,一般先做乘除,再做加减 replace_str = calc_mul_div(strs) #将括号的表达式进行乘除运算,将要处理的字符串strs传入到calc_mul_div乘除的函数 replace_str = calc_add_sub(replace_str) #将括号的表达式进行加减运算 source = format_string(source.replace(strs, replace_str[1:-1])) #将括号的字符串替换为计算结果,结果包含(),替换时去掉():[1:-1] else: #没有括号的表达式在else处理 replace_str = calc_mul_div(source) replace_str = calc_add_sub(replace_str) source = source.replace(source, replace_str) print("my result: ", source.replace("+",""))
乘除的函数
def calc_mul_div(string): #已经把字符串取得,如(49+4*9),要对4*9做运算,如果是(49+4*9-8/2)要对4*9和8/2做运算 regular='\d+\.?\d*([*/]|\*\*)[\-]?\d+\.?\d*' #定规则,从字符串中获取一个乘法和除法的表达式,[\-]?匹配一个负号 while re.findall(regluar, string): #使用findall检测是否有符合规则的字符串,在while循环中,无论有多少乘号或者除号可以计算 expression = re.search(regular, string).group() #相当于得到4*9,放入列表,用search只能取一个,接下来就是要使36替换4*9 if expression.count("*")==1: #如果是乘法 x,y = expression.split("*") #直接使用*为分隔 mul_result = str(float(x) * float(y)) #进行计算 string = string.replace(expression, mul_result) #计算完要替换原来的string(49+4*9-8/2),把36替换4*9,新的string=(49+36-8/2) string = format_string(string) #把string格式化下 #(49+36-8/2)会再进入while re.findall(regular, string),因为里面还有个8/2 if expression.count("/"): #如果是除法,与乘法的一样 x,y = expression.split("/") div_result = str(float(x) / float(y)) string = string.replace(expression, div_result) #得到值string=(49+36+4) string = format_string(string) if expression.count('*')==2: #幂等 x,y=expression.split('**') pow_result=1 for i in range(int(y)): pow_result*=int(x) string=string.replace(expression,str(pow_result)) string=format_string(string) return string #经过一系列的循环检测,经过乘除法后得到string=(49+36+4),这里暂时不忽略幂运算
3.经过乘除计算后,进入加减法
while source.count("(") > 0: strs = re.search('\([^()]*\)', source).group() #去括号,得到括号的字符串(-9-2-5-4*5-6/3-40/8+5*9) replace_str = calc_mul_div(strs) #将括号的表达式进行乘法和除法运算得到(-9-2-5-20-2-5+45),新的replace_str去替换下面加减的replace_str replace_str = calc_add_sub(replace_str) #进行加减运算 source = format_string(source.replace(strs, replace_str[1:-1]))
加减运算调用的函数calc_add_sub
def calc_add_sub(string): #加减法函数中传入的string,如(4+5-6+20-9) add_sub= regular='[\-]?\d+\.?\d*([+-][\-]?\d+\.?\d*' #加减法正则匹配,加减中有个规则:依中序遍历由左而右计算 while re.findall(add_sub, string): add_sub_ex = re.search(add_sub, string).group() if add_sub_ex.count("+")==1: x,y = add_sub_ex.split("+") add_result = str(float(x) + float(y)) string = string.replace(add_sub_ex, add_result) string = format_string(string) if add_sub_ex.count("-"): ##减法时可能出现 -4-5的情况,可能使'-'切分,然后放入列表['',4,5],再把后两个数相加,再替换成-9 sub_list = re.search(add_sub, string) for sub_str in sub_list: numbers = sub_str.split("-") if len(numbers) == 3: result = 0 for v in numbers; if v: result -= float(v) else: x,y = numbers result = float(x) - float(y) string = string.replace(sub_str, "+" + str(result)) string = format_string(string) return string
完整代码
1 [root@python3 day1]# cat calculator.py 2 #!/usr/local/python3/bin/python3 3 import re 4 5 def format_string(string): 6 string = string.replace("--", "+") 7 string = string.replace("-+", "-") 8 string = string.replace("++", "+") 9 string = string.replace("+-", "+") 10 string = string.replace("*-", "*") 11 string = string.replace("*+", "*") 12 string = string.replace("/+", "/") 13 string = string.replace(' ', '') 14 return string 15 16 def calc_mul_div(string): 17 regular='\d+\.?\d*([*/]|\*\*)[\-]?\d+\.?\d*' 18 while re.findall(regluar, string): 19 expression = re.search(regular, string).group() 20 21 if expression.count("*")==1: 22 x,y = expression.split("*") 23 mul_result = str(float(x) * float(y)) 24 string = string.replace(expression, mul_result) 25 string = format_string(string) 26 27 if expression.count("/"): 28 x,y = expression.split("/") 29 div_result = str(float(x) / float(y)) 30 string = string.replace(expression, div_result) 31 string = format_string(string) 32 33 if expression.count('*')==2: 34 x,y=expression.split('**') 35 pow_result=1 36 for i in range(int(y)): 37 pow_result*=int(x) 38 string=string.replace(expression,str(pow_result)) 39 string=format_string(string) 40 return string 41 42 def calc_add_sub(string): 43 add_sub= regular='[\-]?\d+\.?\d*([+-][\-]?\d+\.?\d*' 44 45 while re.findall(add_sub, string): 46 add_sub_ex = re.search(add_sub, string).group() 47 48 if add_sub_ex.count("+")==1: 49 x,y = add_sub_ex.split("+") 50 add_result = str(float(x) + float(y)) 51 string = string.replace(add_sub_ex, add_result) 52 string = format_string(string) 53 54 if add_sub_ex.count("-"): 55 sub_list = re.search(add_sub, string) 56 for sub_str in sub_list: 57 numbers = sub_str.split("-") 58 if len(numbers) == 3: 59 result = 0 60 for v in numbers; 61 if v: 62 result -= float(v) 63 else: 64 x,y = numbers 65 result = float(x) - float(y) 66 string = string.replace(sub_str, "+" + str(result)) 67 string = format_string(string) 68 return string 69 70 def check_expression(string): 71 check_result = True 72 if not string.count("(") == string(")"): 73 print("表达错误,括号示闭合!") 74 check_result = False 75 if re.findall('[a-z]+', string.lower()): 76 print("表达式错误,包含非法字符!") 77 check_result = False 78 return check_result 79 80 source='1 - 2 * ((60-30 +(-40/5) * (-9-2-5-4*5-6/3-40/8+5*9) - (-4*3)/ (16-3*2))' 81 82 if check_expression(source): 83 print("source: ", source) 84 print("eval result: ", eval(source)) 85 source = format_string(source) 86 print(source) 87 88 while source.count("(") > 0: 89 strs = re.search('\([^()]*\)', source).group() 90 replace_str = calc_mul_div(strs) 91 replace_str = calc_add_sub(replace_str) 92 source = format_string(source.replace(strs, replace_str[1:-1])) 93 94 else: 95 replace_str = calc_mul_div(source) 96 replace_str = calc_add_sub(replace_str) 97 source = source.replace(source, replace_str) 98 print("my result: ", source.replace("+",""))
4.做完加减法的运算后,只是处理了最里层的一个括号,还要继续使用while循环处理