使用 Python 解决慈善机构的业务问题

比较不同的编程语言如何解决相同的问题既有趣又具有启发性。接下来是 Python。
65 位读者喜欢这篇文章。
Python programming language logo with question marks

Opensource.com

在我的本系列的第一篇文章中,我描述了一个问题,即将散装物资分成价值相近的食物篮,分发给你社区中生活困难的邻居。我还写了我如何喜欢用各种语言的小程序来解决这样的小问题,并比较它们是如何做到这一点的。

在第一篇文章中,我使用 Groovy 编程语言解决了这个问题。Groovy 在很多方面都像 Python,但在语法上更像 C 和 Java。因此,用 Python 创建相同的解决方案应该是有趣且具有启发性的。

Python 解决方案

在 Java 中,我声明实用程序类来保存数据元组(新的 record 特性将非常有用)。在 Groovy 中,我使用语言对 map 的支持,我在 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 之间的一些差异,有几点值得注意

  1. 在 Python 中,没有关键字用于定义变量 packs;Python 希望第一次使用时设置一个值。
  2. Python 字典键(例如,itembrandunitspricequantity)需要引号来指示它们是字符串;Groovy 假设这些是字符串,但也接受引号。
  3. 在 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。

接下来阅读什么

Python 数据科学入门

使用 Python 进行数据科学为您解析、解释和构建有意义和启发性数据提供了无限的可能性。

标签
Chris Hermansen portrait Temuco Chile
自从 1978 年毕业于不列颠哥伦比亚大学以来,我几乎一直离不开计算机。从 2005 年开始,我成为一名全职 Linux 用户,从 1986 年到 2005 年,我是一名全职 Solaris 和 SunOS 用户,在此之前,我是一名 UNIX System V 用户。

2 条评论

嗨,Chris。谢谢分享。只想提一下可以使用函数 itertools.chain 来展平列表的列表。保重。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.