本文用于练习英文阅读,如有侵权,联系删除
原文链接是Dan Bader的 How to use Python’s min() and max() with nested lists
以下为译文
让我们谈谈在包含另一个列表的列表中使用Python的min()和max()函数。有时这被成为嵌套列表或列表索引。
根据内部特定属性查找列表的最大或最小元素是一种常见情况,但对与Python新手来说可能是一个挑战。
下面是一个具体实例,假设这样一个列表,包含权重部分。
1 | nested_list = [['cherry', 7], ['apple', 100], ['anaconda', 1360]] |
我们希望Python 根据储存在索引1上的权重选择最小和最大元素。我们预期min和max返回下面元素:
min(nested_list) should be ['cherry', 7]
max(nested_list) should be ['anaconda', 1360]
但是仅在nested_list 上调用 min 和 max 我们不能得到预期结果。
我们得到的排序似乎基于存储在索引0处的商品名称:
1 | min(nested_list) |
好吧,为什么选错了元素呢?
让我们停下来思考一下Python的max函数工作原理。算法看起来像这样
1 | def my_max(sequence): |
选择最大值的条件下会发现这个有趣的行为: if item > amximum:如果序列的类型只是int 或 float,它能很好的工作,因为比较他们很简单(它符合我们的直觉,例如 3 > 2)。
但是,如果序列包含其他序列,则情况会变得更加复杂。让我们看一下Python 文档去学习下 比较序列和其他类型
序列对象可以与相同类型的其他对象比较。它们使用 字典顺序 进行比较:首先比较两个序列的第一个元素,如果不同,那么这就决定了比较操作的结果。如果它们相同,就再比较每个序列的第二个元素,以此类推,直到有一个序列被耗尽。如果要比较的两个元素本身就是相同类型的序列,那么就递归进行字典顺序比较。
当max需要比较两个序列并寻找最大的元素,默认可能不是我们想要的。
现在我们了解了为什么会得到意外结果,我们就可以去思考怎样修复代码。
我们如何改变比较行为
我们需要告诉max函数去比较不同的行为。
在上个例子,Python的max函数查看每一个列表的第一个元素(字符串 cherry, apple, or anaconda)并与当前最大元素进行比较,这就是为什么我们仅调用max(nested_list) 它将cherry返回作为最大元素的原因。
怎样告诉max 去比较每个列表的第二个元素呢?
假设我们有一个名叫my_max_by_weight的my_max更新版,它使用第二个元素进行比较每个内嵌列表:
1 | def my_max_by_weight(sequence): |
这很成功,我们可以看到my_max_by_weight 选择了我们预期的最大元素:
1 | my_max_by_weight(nested_list) |
现在假设我们需要在更多类型的list里寻找最大元素。
也许索引(或键)不总是第二项。可能有时是第三,或第四项,或着其他方式查找。
如果我们在my_max里可以复用大部分代码岂不更好?它的某些部分始终相同,例如 检查是否是空序列。
怎样让我们的max()更灵活?
因为Python允许我们把函数当作数据用,所以我们可以吧比较键的代码提取到自己的函数里。我们称之关键函数。我们编写各种不同key 函数,并根据需要传递给my_max。
这让我们的完整的灵活性。不仅可以比较特定列表索引位置,像索引1活着索引2,我们还可以告诉函数比较其他内容,例如,项目名称长度。
让我们看一下具体代码:
1 | def identity(x): |
在代码示例中,您可以看到在默认情况下,如何调用称为identity的键函数,该函数只使用未修改的项进行比较。
将identity 作为key 函数,我们预期my_max 和之前max 行为一样。
1 | nested_list = [['cherry', 7], ['apple', 100], ['anaconda', 1360]] |
并且,我们仍然可以确定和之前(不正确的)结果一样,这很好的表明我们没有完全搞清实现机制。
现在最酷的部分——_我们通过编写key_func 来覆盖比较行为,该key_func 返回第二个子元素而不是元素本身:
1 | def weight(x): |
瞧,这就是我们预期的最大元素!
只是为了演示此重构带来的灵活性,这个key_func 根据每一项的名字的长度,返回最大元素:
1 | def name_length(x): |
这有简写么?
除了用def显式定义key_func 函数外,也可以使用 Python lambda 表达式 去定义匿名函数。这大大缩短了代码(并不会创建命名函数)
1 | my_max(nested_list, key_func=lambda x: x[1]) |
为了让命名更简短(尽管表达力有点差) 假设我们缩短 key_func 参数 key ,并且让我们得出了和Python 的max函数兼容的代码。
这以为这我们不在需要自己重新实现 Python的max函数来查找正确的最大元素:
1 | # This is pure, vanilla Python: |
Python 内置函数min 同样可以正常工作:
1 | min(nested_list, key=lambda x: x[1]) |
它甚至适用于Python的sorted功能,"关键函数"概念对在Python 开发中遇到的很多问题都有价值:
1 | sorted(nested_list, key=lambda x: x[1]) |