在本系列的第一篇文章中,我描述了一个问题,即将大量物资分成价值相近的食品包,分发给社区中生活困难的邻居。我还写了关于我喜欢用各种语言的小程序解决这样的小问题,并比较它们是如何做到这一点的。
在第一篇文章中,我使用 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 袋大米和 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]
第一个列表推导式(赋值给 units)构建字典列表的列表。第二个列表推导式将其“展平”为仅一个字典列表。请注意,Python 和 Groovy 都提供了一个 *
运算符,它在左侧接受一个列表,在右侧接受一个数字 N
,并将列表复制 N
次。
最后一步是将单位重新包装到食品包中以进行分发。与 Groovy 版本一样,您需要更具体地说明理想的食品包价值,并且当您只剩下少量单位时,最好不要过于严格
valueIdeal = 5000
valueMax = valueIdeal * 1.1
好的!重新包装食品包
import random
hamperNumber = 0 # [1]
while len(units) > 0: # [2]
hamperNumber += 1
hamper = []
value = 0
canAdd = True # [2.1]
while canAdd: # [2.2]
u = random.randint(0,len(units)-1) # [2.2.1]
canAdd = False # [2.2.2]
o = 0 # [2.2.3]
while o < len(units): # [2.2.4]
uo = (u + o) % len(units)
unit = units[uo]
unitPrice = unit['price'] # [2.2.4.1]
if len(units) < 3 or not (unit in hamper) and (value + unitPrice) < valueMax:
# [2.2.4.2]
hamper.append(unit)
value += unitPrice
units.pop(u) # [2.2.4.3]
canAdd = len(units) > 0
break # [2.2.4.4]
o += 1 # [2.2.4.5]
# [2.2.5]
print('')
print('Hamper',hamperNumber,'value',value)
for item in hamper:
print('%-25s%-25s%7.2f' % (item['item'],item['brand'],item['price'])) # [2.3]
print('Remaining units',len(units)) # [2.4]
一些澄清,上面注释中括号内的数字(例如,[1])对应于下面的澄清
- 1. 导入 Python 的随机数生成器工具并初始化食品包编号。
- 2. 只要有更多可用的单位,此
while
循环就会将单位重新分配到食品包中
- 2.1 增加食品包编号,获取一个新的空食品包(单位列表),并将其值设置为 0;开始时假设您可以向食品包中添加更多物品。
- 2.2 此
while
循环将尽可能多地向食品包中添加单位(Groovy 代码使用for
循环,但 Python 的for
循环期望迭代某些内容,而 Groovy 具有更传统的 C 形式的for
循环)
- 2.2.1 获取一个介于零和剩余单位数减 1 之间的随机数。
- 2.2.2 假设您找不到更多要添加的单位。
- 2.2.3 创建一个变量,用于表示从起始点开始查找要放入食品包中的物品的偏移量。
- 2.2.4 从随机选择的索引开始,此
while
循环将尝试查找可以添加到食品包中的单位(再次注意,Python 的for
循环可能不适合此处,因为列表的长度将在处理过程中更改)。
- 2.2.4.1. 计算出要查看哪个单位(随机起始点 + 偏移量)并获取其价格。
- 2.2.4.2 如果只剩下少量单位,或者一旦添加该单位,食品包的价值不会太高,则可以将此单位添加到食品包中。
- 2.2.4.3 将单位添加到食品包中,将食品包价值增加单位价格,从可用单位列表中删除该单位。
- 2.2.4.4 只要还有单位剩余,您就可以添加更多单位,因此跳出此循环以继续查找。
- 2.2.4.5 增加偏移量。
- 2.2.5 从此
while
循环退出时,如果您检查了每个剩余单位但找不到可以添加到食品包中的单位,则食品包已完成;否则,您找到了一个单位,可以继续寻找更多单位。
- 2.3 打印出食品包的内容。
- 2.4 打印出剩余单位信息。
当您运行此代码时,输出看起来与 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。
2 条评论