使用 Python 來解決慈善機構(gòu)的業(yè)務(wù)問題
比較不同的編程語言如何解決同一個問題是一個很有趣的事情,也很有指導(dǎo)意義。接下來,我們就來講一講如何用 Python 來解決。
在我這一系列的 第一篇文章 里,我描述了這樣子的一個問題,如何將一大批的救助物資分為具有相同價值的物品,并將其分發(fā)給社區(qū)中的困難住戶。我也曾寫過用不同的編程語言寫一些小程序來解決這樣子的小問題以及比較這些程序時如何工作的。
在第一篇文章中,我是使用了 Groovy 語言來解決問題的。Groovy 在很多方面都與 Python 很相似,但是在語法上她更像 C 語言和 Java。因此,使用 Python 來創(chuàng)造一個相同的解決方案應(yīng)該會很有趣且更有意義。
使用 Python 的解決方案
使用 Java 時,我會聲明一個工具類來保存元組數(shù)據(jù)(新的記錄功能將會很好地用于這個需求)。使用 Groovy 時,我就是用了該語言的映射功能,我也將在 Python 使用相同的機制。
使用一個字典列表來保存從批發(fā)商處批發(fā)來的貨物:
packs = [
{'item':'Rice','brand':'Best Family','units':10,'price':5650,'quantity':1},
{'item':'Spaghetti','brand':'Best Family','units':1,'price':327,'quantity':10},
{'item':'Sardines','brand':'Fresh Caught','units':3,'price':2727,'quantity':3},
{'item':'Chickpeas','brand':'Southern Style','units':2,'price':2600,'quantity':5},
{'item':'Lentils','brand':'Southern Style','units':2,'price':2378,'quantity':5},
{'item':'Vegetable oil','brand':'Crafco','units':12,'price':10020,'quantity':1},
{'item':'UHT milk','brand':'Atlantic','units':6,'price':4560,'quantity':2},
{'item':'Flour','brand':'Neighbor Mills','units':10,'price':5200,'quantity':1},
{'item':'Tomato sauce','brand':'Best Family','units':1,'price':190,'quantity':10},
{'item':'Sugar','brand':'Good Price','units':1,'price':565,'quantity':10},
{'item':'Tea','brand':'Superior','units':5,'price':2720,'quantity':2},
{'item':'Coffee','brand':'Colombia Select','units':2,'price':4180,'quantity':5},
{'item':'Tofu','brand':'Gourmet Choice','units':1,'price':1580,'quantity':10},
{'item':'Bleach','brand':'Blanchite','units':5,'price':3550,'quantity':2},
{'item':'Soap','brand':'Sunny Day','units':6,'price':1794,'quantity':2}]
大米有一包,每包中有 10 袋大米,意大利面條有十包,每包中有一袋意大利面條。上述代碼中,變量 packs
被設(shè)置為 Python 字典列表。這與 Groovy 的方法非常相似。關(guān)于 Groovy 和 Python 之間的區(qū)別,有幾點需要注意:
- 在 Python 中,無需關(guān)鍵字來定義變量
packs
,Python 變量初始化時需要設(shè)置一個值。 - Python 字典中的詞鍵(例如,
item
、brand
、units
、price
、quantity
)需要引號來表明它們是字符串;Groovy 假定這些是字符串,但也接受引號。 - 在 Python 中,符號
{ ... }
表明一個字典聲明; Groovy 使用與列表相同的方括號,但兩種情況下的結(jié)構(gòu)都必須具有鍵值對。
當(dāng)然,表中的價格不是以美元計算的。
接下來,打開散裝包。例如,打開大米的單個散裝包裝,將產(chǎn)出 10 單元大米; 也就是說,產(chǎn)出的單元總數(shù)是 units * quantity
。 Groovy 腳本使用一個名為 collectMany
的方便的函數(shù),該函數(shù)可用于展平列表列表。 據(jù)我所知,Python 沒有類似的東西,所以使用兩個列表推導(dǎo)式來產(chǎn)生相同的結(jié)果:
units = [[{'item':pack['item'],'brand':pack['brand'],
'price':(pack['price'] / pack['units'])}] *
(pack['units'] * pack['quantity']) for pack in packs]
units = [x for sublist in units for x in sublist]
第一個列表可理解為(分配給單元)構(gòu)建字典列表列表。 第二個將其“扁平化”為字典列表。 請注意,Python 和 Groovy 都提供了一個 *
運算符,它接受左側(cè)的列表和右側(cè)的數(shù)字 N
,并復(fù)制列表 N
次。
最后一步是將這些單元的大米之類的重新包裝到籃子(hamper
)中以進行分發(fā)。 就像在 Groovy 版本中一樣,你需要更具體地了解理想的籃子數(shù),當(dāng)你只剩下幾個單元時,你最好不要過度限制,即可以做一些隨機分配:
valueIdeal = 5000
valueMax = valueIdeal * 1.1
很好! 重新打包籃子。
import random
hamperNumber = 0 # 導(dǎo)入 Python 的隨機數(shù)生成器工具并初始化籃子數(shù)
while len(units) > 0: # 只要有更多可用的單元,這個 `while` 循環(huán)就會將單元重新分配到籃子中:
hamperNumber += 1
hamper = []
value = 0
canAdd = True # 增加籃子編號,得到一個新的空籃子(單元的列表),并將其值設(shè)為 0; 開始假設(shè)你可以向籃子中添加更多物品。
while canAdd: # 這個 `while` 循環(huán)將盡可能多地向籃子添加單元(Groovy 代碼使用了 `for` 循環(huán),但 Python 的 `for` 循環(huán)期望迭代某些東西,而 Groovy 則是為更傳統(tǒng)的 C 形式的 `for` 循環(huán)形式):
u = random.randint(0,len(units)-1) # 獲取一個介于 0 和剩余單元數(shù)減 1 之間的隨機數(shù)。
canAdd = False # 假設(shè)你找不到更多要添加的單元。
o = 0 # 創(chuàng)建一個變量,用于從你正在尋找要放入籃子中的物品的起點的偏移量。
while o < len(units): # 從隨機選擇的索引開始,這個 `while` 循環(huán)將嘗試找到一個可以添加到籃子的單元(再次注意,Python `for` 循環(huán)可能不適合這里,因為列表的長度將在迭代中中發(fā)生變化)。
uo = (u + o) % len(units)
unit = units[uo]
unitPrice = unit['price'] # 找出要查看的單元(隨機起點+偏移量)并獲得其價格。
if len(units) < 3 or not (unit in hamper) and (value + unitPrice) < valueMax:
# 如果只剩下幾個,或者添加單元后籃子的價值不太高,你可以將此單元添加到籃子中。
hamper.append(unit)
value += unitPrice
units.pop(u) # 將單元添加到籃子中,按單價增加 籃子數(shù),從可用單元列表中刪除該單元。
canAdd = len(units) > 0
break # 只要還有剩余單元,你就可以添加更多單元,因此可以跳出此循環(huán)繼續(xù)尋找。
o += 1 # 增加偏移量。
# 在退出這個 `while` 循環(huán)時,如果你檢查了所有剩余的單元并且找不到單元可以添加到籃子中,那么籃子就完成了搜索; 否則,你找到了一個,可以繼續(xù)尋找更多。
print('')
print('Hamper',hamperNumber,'value',value)
for item in hamper:
print('%-25s%-25s%7.2f' % (item['item'],item['brand'],item['price'])) # 打印出籃子的內(nèi)容。
print('Remaining units',len(units)) # 打印出剩余的單元信息。
一些澄清如上面的注釋。
運行此代碼時,輸出看起來與 Groovy 程序的輸出非常相似:
Hamper 1 value 5304.0
UHT milk Atlantic 760.00
Tomato sauce Best Family 190.00
Rice Best Family 565.00
Coffee Colombia Select 2090.00
Sugar Good Price 565.00
Vegetable oil Crafco 835.00
Soap Sunny Day 299.00
Remaining units 148
Hamper 2 value 5428.0
Tea Superior 544.00
Lentils Southern Style 1189.00
Flour Neighbor Mills 520.00
Tofu Gourmet Choice 1580.00
Vegetable oil Crafco 835.00
UHT milk Atlantic 760.00
Remaining units 142
Hamper 3 value 5424.0
Soap Sunny Day 299.00
Chickpeas Southern Style 1300.00
Sardines Fresh Caught 909.00
Rice Best Family 565.00
Vegetable oil Crafco 835.00
Spaghetti Best Family 327.00
Lentils Southern Style 1189.00
Remaining units 135
...
Hamper 21 value 5145.0
Tomato sauce Best Family 190.00
Tea Superior 544.00
Chickpeas Southern Style 1300.00
Spaghetti Best Family 327.00
UHT milk Atlantic 760.00
Vegetable oil Crafco 835.00
Lentils Southern Style 1189.00
Remaining units 4
Hamper 22 value 2874.0
Sardines Fresh Caught 909.00
Vegetable oil Crafco 835.00
Rice Best Family 565.00
Rice Best Family 565.00
Remaining units 0
最后一個籃子在內(nèi)容和價值上有所簡化。
結(jié)論
乍一看,這個程序的 Python 和 Groovy 版本之間沒有太大區(qū)別。 兩者都有一組相似的結(jié)構(gòu),這使得處理列表和字典非常簡單。 兩者都不需要很多“樣板代碼”或其他“繁雜”操作。
此外,使用 Groovy 時,向籃子中添加單元還是一件比較繁瑣的事情。 你需要在單元列表中隨機選擇一個位置,然后從該位置開始,遍歷列表,直到找到一個價格允許的且包含它的單元,或者直到你用完列表為止。 當(dāng)只剩下幾件物品時,你需要將它們?nèi)拥阶詈笠粋€籃子里。
另一個值得一提的問題是:這不是一種特別有效的方法。 從列表中刪除元素、極其多的重復(fù)表達式還有一些其它的問題使得這不太適合解決這種大數(shù)據(jù)重新分配問題。 盡管如此,它仍然在我的老機器上運行。
如果你覺得我在這段代碼中使用 while
循環(huán)并改變其中的數(shù)據(jù)感到不舒服,你可能希望我讓它更有用一些。 我想不出一種方法不使用 Python 中的 map 和 reduce 函數(shù),并結(jié)合隨機選擇的單元進行重新打包。 你可以嗎?
在下一篇文章中,我將使用 Java 重新執(zhí)行此操作,以了解 Groovy 和 Python 的工作量減少了多少,未來的文章將介紹 Julia 和 Go。