周末没事做,看到隔壁老王和他媳妇玩二十一点,就进屋看看。我发现老王真的不行,那个不行,这个不行。他连24点都不能陪老婆玩。如果他生妻子的气,他什么都不能满足,不能这样,不能那样。我坐下来和他儿媳妇打了两局,都是首屈一指的,而且都是我赢的!临走时,他老婆让我多玩几局,挺有意思的。为了让老王在他老婆面前抬头,我决定帮他……我用python写了一个24点的计算,老王对我很感激。什么是24分?我们先约定一下老王夫妇玩的24点规则:给定4个任意数(0-9),然后用+,-,*,/从这4个数中计算出24。小时候,我就是玩这个规矩的。长大了,有了根号,有了各种莫名其妙的高级算法。这不再有趣了,因为我不知道该怎么做。可能有人觉得很简单,但是真的很简单吗?例如:8,3,3,37,3,3,3你能一眼看出答案吗?好像真的可以……大致这样想,把四个数字全排列起来,中间加上运算符号。我们需要对运算符进行排列组合,因为只有四个数,所以只需要三个运算符,而且运算符可能会重复,比如三个都是+。然后遍历四个数的全排列,对每组数,遍历所有组合算子。最后,将数字和运算符连接起来得到最终结果。演示环境操作系统:windows10Python版本:python3.7代码编辑器:pycharm2018.2使用的模块:math、itertools、collections.abc具体代码1.首先我们对所有数字进行全排列。这里我们使用itertools.permutations来帮助我们完成。iertools.permutations使用演示fromitertoolsimportpermutationsdata_list=permutations([1,2,3,4],2)fordata_list中的数据:print(data)结果显示(1,2)(1,3)(1,4)(2,1)(2,3)(2,4)(3,1)(3,2)(3,4)(4,1)(4,2)(4,3)排列第一个参数It是一个接收类迭代的对象,第二个参数指定每次排列从类迭代对象中选择多少个字符。第二个参数也可以不传,所以默认就是可迭代对象的长度。并返回一个生成器。所以我们需要把所有的数全部排列好,可以这样写:defget_all_data_sequence(data_iter):returnpermutations(data_iter)2然后我们需要得到所有运算符的所有组合。这里我们将使用itertools.product函数。itertools.product使用演示fromitertoolsimportproductsequence1=product('ABCD','xy')sequence2=product([0,1],repeat=3)forsequenceinsequence1:print(sequence)print('-'*30)forsequenceinsequence2:print(sequence)结果显示('A','x')('A','y')('B','x')('B','y')('C','x')('C','y')('D','x')('D','y')-------------------------------(0,0,0)(0,0,1)(0,1,0)(0,1,1)(1,0,0)(1,0,1)(1,1,0)(1,1,1)itertools.product,返回所有传入序列的笛卡尔积的元组,repeat参数表示传入序列的重复次数.返回的是一个生成器。然后你可以通过这个函数defget_all_operations_sequence()得到所有的操作符:operations=['+','-','*','/']returnproduct(operations,repeat=3)3.现在我们有运算符和数字的所有可能组合,我们需要将它们拼接在一起。然后执行操作。对于这一步,我们将使用itertools.zip_longest()和itertools.chain.form_iterable()函数。itertools.zip_longest()用法演示data=zip_longest([1,2,3,4],['*','-','+'],fillvalue='')forvalueindata:print(value)结果Display(1,'*')(2,'-')(3,'+')(4,'')zip_longest()其实和python内置的zip()函数类似,只不过zip_longest是最长的以一个序列为基础,用fillvalue参数itertools.chain.form_iterable()的值填充缺失值使用演示data=zip_longest([1,2,3,4],['*','-','+'],fillvalue='')data_chain=chain.from_iterable(data)forvalueindata_chain:print(value)结果显示这里的数据是1*2-3+4,大家懂的在这里输入种数据,然后我们将数据传入chain.form_iterable(),它就可以一个一个的取出里面的值了。了解了这两个函数之后,我们就可以开始拼接数字和运算符了。defcalculate(self):'''计算值,返回对应的表达式和值:return:'''fordata_sequenceinget_all_data_sequence():operation_sequences=get_all_operation_sequence()foroperation_sequenceinoperation_sequences:value=zip_longest(data_sequence,operation_sequence,fillvalue='')value_chain=chain.from_iterable(value)calculate_str=''#将得到的字符拼接成一个表达式calculate_strfor_invalue_chain:calculate_str+=_try:result=eval(calculate_str#处理被除数可能为零,然后就跳过这个循环exceptZeroDivisionError:continueifmath.isclose(result,24):returncalculate_str,resultreturnNone,None代码分析1.eval()函数接受一个字符串,可以让这个字符串像python一样运行代码,并返回运行结果。2.math.isclose():为什么这里需要使用math.isclose()而不是直接使用==运算符?这是因为最终计算出的表达式可能存在精度问题,比如23.9...或者24.0...等数字,所以我们需要使用math.isclose()函数来帮助我们判断两个数字是否相等。这个函数有一个精度范围。这样,当出现上述情况时,我们也可以进行条件匹配。我们运行代码,然后测试代码是否执行我们想要的操作。首先我们测试1、2、3、4这四个数,程序出来的结果是1*2*3*424。看来我们写的代码是正确的。我们来测试一组数据8,8,3,3嗯?我们没有得到结果?这四个数字不能算出24吗?8/(3-8/3)这个组合没问题,为什么这个结果没有计算出来?这是因为我们没有考虑括号。括号可以改变操作的优先级。所以我们必须考虑括号。那么想想最多可以有多少个括号呢?如何给我们的表达式添加括号?在4个数字的运算中,括号不能超过三个。并且,这里,我们使用一种简单的方法来添加括号,我们列出所有可能出现括号的情况,然后将得到的运算表达式拼接进去。你可能会认为列出所有括号出现的情况是不现实的,因为有很多情况其实不是这样的。当我们列出它们时,您会发现只有11种情况。FORM_STRS=[#numberoperatornumberoperatornumberoperatornumber#括号的大小写'(%s%s%s)%s%s%s%s','(%s%s%s%s%s)%s%s','(%s%s%s%s%s%s%s)','%s%s(%s%s%s)%s%s','%s%s(%s%s%s%s%s)','%s%s%s%s(%s%s%s)',#两个括号的大小写'(%s%s%s)%s(%s%s%s)','((%s%s%s)%s%s)%s%s','(%s%s(%s%s%s))%s%s','%s%s((%s%s%s)%s%s)','%s%s(%s%s(%s%s%s))',#三个括号重复了,所以没有需要列出来】然后我们就是遍历拼接得到的表达式,然后计算表达式。这样,我们就可以得到正确的结果。代码写好了,终于可以开始陪老婆玩了,哦不,是老王家的儿媳妇在玩。代码已经上传到Github:https://github.com/MiracleYou...关注公众号“Python专栏”,更多好玩有趣的Python等着你
