使用 Python 來解決慈善機構的業務問題
在我這一系列的 第一篇文章 里,我描述了這樣子的一個問題,如何將一大批的救助物資分為具有相同價值的物品,並將其分發給社區中的困難住戶。我也曾寫過用不同的編程語言寫一些小程序來解決這樣子的小問題以及比較這些程序時如何工作的。
在第一篇文章中,我是使用了 Groovy 語言來解決問題的。Groovy 在很多方面都與 Python 很相似,但是在語法上她更像 C 語言和 Java。因此,使用 Python 來創造一個相同的解決方案應該會很有趣且更有意義。
使用 Python 的解決方案
使用 Java 時,我會聲明一個工具類來保存元組數據(新的記錄功能將會很好地用於這個需求)。使用 Groovy 時,我就是用了該語言的映射功能,我也將在 Python 使用相同的機制。
使用一個字典列表來保存從批發商處批發來的貨物:
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
被設置為 Python 字典列表。這與 Groovy 的方法非常相似。關於 Groovy 和 Python 之間的區別,有幾點需要注意:
- 在 Python 中,無需關鍵字來定義變數
packs
,Python 變數初始化時需要設置一個值。 - Python 字典中的詞鍵(例如,
item
、brand
、units
、price
、quantity
)需要引號來表明它們是字元串;Groovy 假定這些是字元串,但也接受引號。 - 在 Python 中,符號
{ ... }
表明一個字典聲明; Groovy 使用與列表相同的方括弧,但兩種情況下的結構都必須具有鍵值對。
當然,表中的價格不是以美元計算的。
接下來,打開散裝包。例如,打開大米的單個散裝包裝,將產出 10 單元大米; 也就是說,產出的單元總數是 units * quantity
。 Groovy 腳本使用一個名為 collectMany
的方便的函數,該函數可用於展平列表列表。 據我所知,Python 沒有類似的東西,所以使用兩個列表推導式來產生相同的結果:
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]
第一個列表可理解為(分配給單元)構建字典列表列表。 第二個將其「扁平化」為字典列表。 請注意,Python 和 Groovy 都提供了一個 *
運算符,它接受左側的列表和右側的數字 N
,並複製列表 N
次。
最後一步是將這些單元的大米之類的重新包裝到籃子(hamper
)中以進行分發。 就像在 Groovy 版本中一樣,你需要更具體地了解理想的籃子數,當你只剩下幾個單元時,你最好不要過度限制,即可以做一些隨機分配:
valueIdeal = 5000
valueMax = valueIdeal * 1.1
很好! 重新打包籃子。
import random
hamperNumber = 0 # 導入 Python 的隨機數生成器工具並初始化籃子數
while len(units) > 0: # 只要有更多可用的單元,這個 `while` 循環就會將單元重新分配到籃子中:
hamperNumber += 1
hamper = []
value = 0
canAdd = True # 增加籃子編號,得到一個新的空籃子(單元的列表),並將其值設為 0; 開始假設你可以向籃子中添加更多物品。
while canAdd: # 這個 `while` 循環將儘可能多地向籃子添加單元(Groovy 代碼使用了 `for` 循環,但 Python 的 `for` 循環期望迭代某些東西,而 Groovy 則是為更傳統的 C 形式的 `for` 循環形式):
u = random.randint(0,len(units)-1) # 獲取一個介於 0 和剩餘單元數減 1 之間的隨機數。
canAdd = False # 假設你找不到更多要添加的單元。
o = 0 # 創建一個變數,用於從你正在尋找要放入籃子中的物品的起點的偏移量。
while o < len(units): # 從隨機選擇的索引開始,這個 `while` 循環將嘗試找到一個可以添加到籃子的單元(再次注意,Python `for` 循環可能不適合這裡,因為列表的長度將在迭代中中發生變化)。
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) # 將單元添加到籃子中,按單價增加 籃子數,從可用單元列表中刪除該單元。
canAdd = len(units) > 0
break # 只要還有剩餘單元,你就可以添加更多單元,因此可以跳出此循環繼續尋找。
o += 1 # 增加偏移量。
# 在退出這個 `while` 循環時,如果你檢查了所有剩餘的單元並且找不到單元可以添加到籃子中,那麼籃子就完成了搜索; 否則,你找到了一個,可以繼續尋找更多。
print('')
print('Hamper',hamperNumber,'value',value)
for item in hamper:
print('%-25s%-25s%7.2f' % (item['item'],item['brand'],item['price'])) # 列印出籃子的內容。
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
最後一個籃子在內容和價值上有所簡化。
結論
乍一看,這個程序的 Python 和 Groovy 版本之間沒有太大區別。 兩者都有一組相似的結構,這使得處理列表和字典非常簡單。 兩者都不需要很多「樣板代碼」或其他「繁雜」操作。
此外,使用 Groovy 時,向籃子中添加單元還是一件比較繁瑣的事情。 你需要在單元列表中隨機選擇一個位置,然後從該位置開始,遍歷列表,直到找到一個價格允許的且包含它的單元,或者直到你用完列表為止。 當只剩下幾件物品時,你需要將它們扔到最後一個籃子里。
另一個值得一提的問題是:這不是一種特別有效的方法。 從列表中刪除元素、極其多的重複表達式還有一些其它的問題使得這不太適合解決這種大數據重新分配問題。 儘管如此,它仍然在我的老機器上運行。
如果你覺得我在這段代碼中使用 while
循環並改變其中的數據感到不舒服,你可能希望我讓它更有用一些。 我想不出一種方法不使用 Python 中的 map 和 reduce 函數,並結合隨機選擇的單元進行重新打包。 你可以嗎?
在下一篇文章中,我將使用 Java 重新執行此操作,以了解 Groovy 和 Python 的工作量減少了多少,未來的文章將介紹 Julia 和 Go。
via: https://opensource.com/article/20/9/solve-problem-python
作者:Chris Hermansen 選題:lujun9972 譯者:zepoch 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive