Python 中 sorted 如何自定义比较逻辑
作者:zikcheng 发布时间:2021-09-16 23:43:37
在 Python 中对一个可迭代对象进行排序是很常见的一个操作,一般会用到 sorted() 函数
num_list = [4, 2, 8, -9, 1, -3]
sorted_num_list = sorted(num_list)
print(sorted_num_list)
上面的代码是对整数列表 num_list 按从小到大的顺序进行排序,得到的结果如下
[-9, -3, 1, 2, 4, 8]
有时候不仅仅是对元素本身进行排序,而是在元素值的基础上进行一些计算之后再进行比较,比如将 num_list 中的元素按照其平方值的大小进行排序。
在 Python 2 中,可以通过 sorted() 函数中的 cmp 或 key 参数来实现这种自定义的比较逻辑。cmp 比较函数接收两个参数 x 和 y(x 和 y 都是列表中元素)并且返回一个数字,如果返回正数表示 x > y,返回 0 表示 x == y,返回负数表示 x < y。key 函数接收一个参数,重新计算出一个结果,然后用计算出的结果参与排序比较。因此在 Python 2 中按平方值大小排序可以有下面两种实现方式
num_list = [4, 2, 8, -9, 1, -3]
# cmp 参数只在 Python 2 中存在,Python 3 及之后的版本移除了 cmp 参数
sorted_num_list = sorted(num_list, cmp=lambda x, y: x ** 2 - y ** 2)
sorted_num_list = sorted(num_list, key=lambda x: x ** 2)
但是随着 Python 3.0 的发布,cmp 参数也随之被移除了,也就是说在 Python 3 中自定义比较逻辑就只能通过 key 参数来实现。至于为什么将 cmp 参数移除,在 Python 的 Issue tracker 中有一段很长的讨论,主要有以下两点原因
cmp 是一个冗余参数,所有使用 cmp 的场景都可以用 key 来代替
使用 key 比使用 cmp 的性能更快,对于有 N 个元素的列表,在排序过程中如果调用 cmp 进行比较,那么 cmp 的调用次数为 Nlog(N) 量级(基于比较的排序的最快时间复杂度),如果使用 key 参数,那么只需要在每个元素上调用一次 key 函数,只有 N 次调用,虽然使用 key 参数也要进行 O(Nlog(N)) 量级比较次数,但这些比较是在 C 语言层,比调用用户自定义的函数快。
关于上面性能的问题,我做了一个实验,分别随机生成 1000、10000、100000 和 1000000 个整数,然后用 key 和 cmp 的方式分别进行排序并记录排序的时间消耗
import random
import time
counts = (1000, 10000, 100000, 1000000)
def custom_cmp(x, y):
return x ** 2 - y ** 2
def custom_key(x):
return x ** 2
print('%7s%20s%20s' % ('count', 'cmp_duration', 'key_duration'))
for count in counts:
min_num = -count // 2
max_num = count // 2
nums = [random.randint(min_num, max_num) for _ in range(count)]
start = time.time()
sorted(nums, cmp=custom_cmp)
cmp_duration = time.time() - start
start = time.time()
sorted(nums, key=custom_key)
key_duration = time.time() - start
print('%7d%20.2f%20.2f' % (count, cmp_duration, key_duration))
在我的笔记本上一次运行结果如下
count cmp_duration key_duration
1000 0.00 0.00
10000 0.02 0.01
100000 0.34 0.11
1000000 4.75 1.85
可以看到,当列表中数字的数量超过 100000 的时候,使用 key 函数的性能优势就非常明显了,比 cmp 快了 2~3 倍。
对于熟悉 Java 或 C++ 等其他编程语言的同学来说,可能更熟悉 cmp 的比较方式。其实 Python 3 中也可以通过 functools 工具包中的 cmp_to_key() 函数来将 cmp 转换成 key,从而使用接收两个参数的自定义比较函数 cmp。
import functools
num_list = [4, 2, 8, -9, 1, -3]
def custom_cmp(x, y):
return x ** 2 - y ** 2
sorted_num_list = sorted(num_list, key=functools.cmp_to_key(custom_cmp))
print(sorted_num_list)
那么,cmp_to_key() 函数是如何将 cmp 转换成 key 的呢,我们可以通过源码一探究竟
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K
其实 cmp_to_key() 返回的是一个类 K,只不过在类 K 中重载了各种比较运算符,重载的过程中使用到了自定义的比较函数 mycmp,使得 K 的大小比较逻辑与 mycmp 一致。这样,对于 num_list 中的每个元素 num 都会执行一次 K(num) 生成一个类 K 的实例,然后通过比较不同 K 的实例的大小进行排序。
虽然通过 cmp_to_key() 可以调用自定义的 cmp 函数,但是还是要优先使用 key 函数,因为通过 cmp_to_key() 方式会在排序过程中创建很多类 K 的实例,对性能有很大影响,下面是 cmp_to_key() 和 key 的性能比较
count cmp_to_key key_duration
1000 0.01 0.00
10000 0.10 0.01
100000 1.36 0.09
1000000 16.89 1.13
当 num_list 中的数量为 1000000 的时候 key 比 cmp_to_key 快了将近 15 倍。
本文主要介绍了如何在 sorted 函数中自定义比较逻辑,Python 2 中可以通过 cmp 或 key 来实现,cmp 接收 2 个参数,通过返回的数值来判断两个参数的大小,key 重新计算一个新的结果参与比较。在 Python 3 中,考虑到 cmp 的性能和冗余的原因,将其移除了。在 Python 3.2 中提供了 functools.cmp_to_key 这个函数来使用自定义的比较函数 cmp,但是出于性能的考虑,我们还是要优先使用 key 来进行排序。
来源:https://www.cnblogs.com/zikcheng/p/14360071.html
猜你喜欢
- LCase:转成小写 UCase:转成大写 下面是ASP中的代码,可以直接演示效果的。 代码如下:<% dim s
- format函数的调用format函数可以被直接调用或在print函数中以占位符实现格式化调用。在**2.6**版本前,占位符仍和其他语言一
- 一、MySQL中如何表示当前时间?其实,表达方式还是蛮多的,汇总如下:CURRENT_TIMESTAMPCURRENT_TIMESTAMP(
- 本博客演示使用OpenCV和PIL读取和显示图像的差异。首先来看一下原始的bgr图像 src.jpg1. 使用cv2读取src.jpg并转为
- 目录优化排序查询避免重复获取刚刚修改的数据行懒加载的联合查询优化排序查询自定义变量的一个重要特性是你可以同时将该变量的数学计算后的结果再赋值
- 下面这段代码能够显示,当前用户所能够看到的所有的用户和表有兴趣的, 可以把每个表的内容加上<% Dim objOraSess
- 1、问题:安装VSCode后打开,发现显示的语言为英文,想显示为中文?2、解决方法:2.1、快捷键CTRL+SHIFT+P,找到如下设置Co
- 用下面代码可实现:<%Dim writeDim fileSysObj, tf, readrea
- 一、要求 1 创建数据表 CREATE TABLE [dbo].[StuScore]( [stuid] [int] NOT NULL, [s
- 一:mysql官网下载https://dev.mysql.com/downloads/file/?id=494993
- private void Button1_Click(object sender, System.E
- UPDATE是否会加锁?SQL语句为如下时,是否会加锁?UPDATE table1 SET num = num + 1 WHERE id=1
- 本文实例讲述了Python递归及尾递归优化操作。分享给大家供大家参考,具体如下:1、递归介绍递归简而言之就是自己调用自己。使用递归解决问题的
- python如何将json格式的数据快速的转化成指定格式的数据呢?或者转换成sql文件?下面的例子是将json格式的数据准换成以#_#分割的
- 我们都知道,python可以通过threading module来创建新的线程,然而在创建线程的线程(父线程)关闭之后,相应的子线程可能却没
- Truncate是SQL中的一个删除数据表内容的语句,用法是:TRUNCATE TABLE [Table Name]。下面是对Truncat
- 其中 offset and fetch 最重要的新特性是 用来 分页,既然要分析 分页,就肯定要和之前的分页方式来比较了,特别是 Row_N
- 1.获取当前时间var myDate = new Date();2.获取时间中的年月日时分秒myDate.getYear();
- 利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信。首先,先了解下SocketServer模块中可供使用的类:Bas
- 大家在使用PyCharm的过程中,肯定会遇到各种各样的问题,其中一个问题就是很多第三方的包安装不了。在使用过程中,我对这种情况进行了总结,现