用Python算24點

小外甥女的課後作業是算24點,看了一下題目,發現都挺難的,像下面這些:
7 7 3 3
8 8 3 3
5 5 5 1
1 5 7 10
2 5 5 10
只能用加減乘除,算出24點。
發現心算不容易,於是突發奇想,用Python寫了一個程式來算。
基本思路
列舉4個數字可以組成的所有的算式,找到其中等於24的式子。
對於每一個算式,我們用一棵二叉樹來存取。根節點儲存運算子(+,-,*,/),左子樹儲存運算子左側的子算式,右子樹儲存運算子右側的子算式,運算結果也存在根節點中。如下圖
這棵二叉樹對應的算式就是 (4 + 10) + (2 * 5) 。非常簡單直觀。
有了二叉樹後,對於給定的一組數字,我們就可以遞迴地列出這組數字組成的所有可能的算式。
具體實現
首先定義二叉樹。對於樹中的每一個節點,我們用一個Node類來儲存
class Node (object):
def __init__ (self, result=None):
self._left = None
self._right = None
self._operator = None
self._result = result
def set_expression (self, left_node, right_node, operator):
self._left = left_node
self._right = right_node
self._operator = operator
expression = "{} {} {}" .format(left_node._result, operator, right_node._result)
self._result = eval(expression)
def __repr__ (self):
if self._operator:
return '<Node operator="{}">' .format(self._operator)
else :
return '<Node value="{}">' .format(self._result)
Node類中,如果 _operator 是None,則 _result 就是數字本身,如果 _operator 不為None,則 _result 表示的是左右兩棵子樹運算的結果。
對於一組給定順序的數字,我們用遞迴的方式獲取所有可能的算式
def build_all_trees (array):
if len(array) == 1 :
tree = Node(array[ 0 ])
return [tree]
treelist = []
for i in range( 1 , len(array)):
left_array = array[:i]
right_array = array[i:]
left_trees = build_all_trees(left_array)
right_trees = build_all_trees(right_array)
for left_tree in left_trees:
for right_tree in right_trees:
combined_trees = build_tree(left_tree, right_tree)
treelist.extend(combined_trees)
return treelist
上面函式的輸入是一組數字,第一層for迴圈中將這組數字,拆成左右兩部分,分別對應左右兩棵子樹的部分,輸出的 treelist 是所有可能的算式。
對於給定的左子樹和右子樹,build_tree 函式用加減乘除把它們連線在一起,組成新的二叉樹。build_tree 函式如下
def build_tree (left_tree, right_tree):
treelist = []
tree1 = Node()
tree1.set_expression(left_tree, right_tree, "+" )
treelist.append(tree1)
tree2 = Node()
tree2.set_expression(left_tree, right_tree, "-" )
treelist.append(tree2)
tree4 = Node()
tree4.set_expression(left_tree, right_tree, "*" )
treelist.append(tree4)
if right_tree._result != 0 :
tree5 = Node()
tree5.set_expression(left_tree, right_tree, "/" )
treelist.append(tree5)
return treelist
build_tree 中會列舉所有的運算方式,組成新的二叉樹並返回所有可能的組合。
這裡需要注意的是,如果運算方式是除法,除數也就是右側子算式的結果不能為0。
羅列出所有的算式後,我們就來找一找有沒有算式的結果是24。
def find_24 (array):
perms = itertools.permutations(array)
found = False
for perm in perms:
treelist = build_all_trees(perm)
for tree in treelist:
if math.isclose(tree._result, 24 , rel_tol= 1e-10 ):
expression = get_expression(tree)
print( "{} - {}" .format(perm, expression))
found = True
break
if found:
break
以上就實現了我們的演算法。
測試
下面是令人興奮的時刻。我們用文章開始的幾個例子來測一下我們的演算法。
執行結果如下
( 7 , 7 , 3 , 3 ) - ( 7 * (( 3 / 7 ) + 3 ))
( 8 , 8 , 3 , 3 ) - ( 8 / ( 3 - ( 8 / 3 )))
( 5 , 5 , 5 , 1 ) - (( 5 - ( 1 / 5 )) * 5 )
( 1 , 5 , 7 , 10 ) - (( 1 + ( 7 / 5 )) * 10 )
( 2 , 5 , 5 , 10 ) - (( 5 - ( 2 / 10 )) * 5 )
哈哈,都用到了小數運算,怪不得心算這麼難呢~ 是不是很有趣?
原文釋出時間為:2018-11-16
本文作者:shenzhongqiang
本文來自雲棲社群合作伙伴“ ofollow,noindex">Python愛好者社群 ”,瞭解相關資訊可以關注“ Python愛好者社群 ”。