一、問題
碰到一個比較好玩的問題,我有許多小額的發票,需要從這些發票中湊出一個指定的整數來。怎么去實現呢?
二、規划求解
在excel中,有一個功能是“規划求解”,具體可以參看鏈接:規划求解。
規划求解怎么算的,我也不知道,我們來看看用python怎么實現。
三、python實現
這些小額發票,我們可以用一個列表來表示。比如a = [1, 3, 5, 6, 8]。那么這個列表的元素可能產生多少種組合呢?由於列表里面每一個元素都可能有,也可能沒有。所以這里面就有2的5次方種變化。代碼驗證(ps:代碼參考CSDN)。
a = [1, 3, 5, 6, 8]
all_subset = [[]]
for i in range(len(a)):
for j in range(len(all_subset)):
all_subset.append(all_subset[j]+[a[i]])
print(all_subset)
print(len(all_subset)) # 32
似乎我只需要對上面的二維數組里面每一個元素,即一維數組求和即可。列表中有5個數,有32種可能性,這么操作可行。但是,假如是10個數則有1024(2的10次方)種可能性,20個數就有1048576(2的20次方)種可能性,30個數則有1073741824(2的30次方)種可能性。組合的可能性可是指數翻番,電腦根本存不下這么長的列表。那就得再想其它辦法。
現在的問題是,在沒有這個長列表的情況下,我怎么去遍歷這些所有組合呢。我們用多層嵌套的方式求解。
a = [1, 3, 5, 6, 8]
ran2 = range(2)
sum = 0
for d0 in ran2:
for d1 in ran2:
for d2 in ran2:
for d3 in ran2:
for d4 in ran2:
data = a[0]*d0 + a[1]*d1 + a[2]*d2 + a[3]*d3 + a[4]*d4
sum += 1
print(sum) # 32
這樣我們就不用存這個長列表,只要把每一個data跟設定的總數進行對比即可。但是問題又來了,如果給了我30張發票,我要寫30個嵌套循環嗎?那畫面簡直無法想象。
我們注意到,列表里面每個元素,只有兩種狀態,即有和沒有。那么可以再創建一個列表,表示狀態,0表示沒有,1表示有。即
a = [1, 3, 5, 6, 8]
coefficient = [0, 0, 0, 0, 0]
這樣就只需要更改coefficient里面每個元素的狀態即可。為了實現coefficient這個列表各種可能性,我們可以采用把數字轉為二進制,然后將二進制數每個數字位分開的方式。
代碼如下。
a = [1, 3, 5, 6, 8]
for i in range(0, 2**len(a)):
coefficient = list(str(bin(i))[2:].rjust(len(a), '0'))
print(coefficient)
結果如下:
['0', '0', '0', '0', '0']
['0', '0', '0', '0', '1']
['0', '0', '0', '1', '0']
['0', '0', '0', '1', '1']
['0', '0', '1', '0', '0']
....
['1', '1', '1', '1', '0']
['1', '1', '1', '1', '1']
這樣就只需要把列表a和列表coefficient的每個元素相乘累加即可。
四、代碼
最后呈現的代碼如下,我有29張發票,要求得到的值是180。
#!/usr/bin/env python
# coding=utf-8
import time
from functools import reduce
# 原列表
nums = [1.68, 37.27, 14.78, 12.84, 15.26, 4.86, 10.06, 25.6, 8.42, 20.27,
6.46, 29.68, 6.13, 18.48, 4.98, 7.88, 4.77, 8.61, 5.75, 4.3, 7.76,
3.13, 11.11, 5.1, 2.91, 3.55, 6.45, 55.27, 8.76]
# 期待求和的值
expect_data = 180
# 對數組進行排序
nums.sort()
# 縮小數組范圍,將比設定值大的元素拋棄
narrow_nums = []
for i in nums:
if i > expect_data:
break
narrow_nums.append(i)
# 把數組倒序
narrow_nums.sort(reverse=True)
start_time = time.time()
# 開始計算
for i in range(0, 2**len(narrow_nums)):
# 將循環的值轉變為參數列表
nums_coefficient = list(str(bin(i))[2:].rjust(len(narrow_nums), '0'))
# 將參數列表和原數組進行乘積運算得到新的列表
combine_list = list(map(lambda x: x[0] * int(x[1]), zip(narrow_nums, nums_coefficient)))
# 對列表進行求和取值
sumdata = reduce(lambda x, y: x + y, combine_list)
if abs(sumdata - expect_data) < 1e-6:
print(list(filter(lambda x: x > 0, combine_list)))
break
end_time = time.time()
print('運算了{}次,耗時{}'.format(i, round(end_time - start_time, 2)))
以下是結果:
[20.27, 18.48, 14.78, 12.84, 11.11, 10.06, 8.76, 8.61, 8.42, 7.88, 7.76, 6.46, 6.45, 5.75, 5.1, 4.98, 4.86, 4.77, 4.3, 3.55, 3.13, 1.68]
運算了29359101次,耗時591.0
五、總結
- 介紹了如何將多層嵌套轉化為一維數組進行運算
- 本文采用暴力破解,耗時較久。拋磚引玉,歡迎大家留言,提供節省時間復雜度的算法。