Python 中 sorted 如何自定义比较逻辑


Posted in Python onFebruary 02, 2021

在 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 来进行排序。

以上就是Python 中 sorted 如何自定义比较逻辑的详细内容,更多关于python sorted自定义比较逻辑的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
天翼开放平台免费短信验证码接口使用实例
Dec 18 Python
为Python的web框架编写MVC配置来使其运行的教程
Apr 30 Python
Python功能键的读取方法
May 28 Python
Python实现的异步代理爬虫及代理池
Mar 17 Python
python获取当前运行函数名称的方法实例代码
Apr 06 Python
python编程实现随机生成多个椭圆实例代码
Jan 03 Python
python 将字符串转换成字典dict的各种方式总结
Mar 23 Python
Python代码打开本地.mp4格式文件的方法
Jan 03 Python
Python计算指定日期是今年的第几天(三种方法)
Mar 26 Python
Anaconda的安装及其环境变量的配置详解
Apr 22 Python
python pygame 愤怒的小鸟游戏示例代码
Feb 25 Python
用python自动生成日历
Apr 24 Python
Python实现钉钉/企业微信自动打卡的示例代码
Feb 02 #Python
手把手教你配置JupyterLab 环境的实现
Feb 02 #Python
python 模块导入问题汇总
Feb 01 #Python
用python制作个视频下载器
Feb 01 #Python
python基于pexpect库自动获取日志信息
Feb 01 #Python
Python入门基础之数字字符串与列表
Feb 01 #Python
Pyecharts 中Geo函数常用参数的用法说明
Feb 01 #Python
You might like
新版PHP极大的增强功能和性能
2006/10/09 PHP
一个php作的文本留言本的例子(六)
2006/10/09 PHP
discuz 首页四格:最新话题+最新回复+热门话题+精华文章插件
2007/08/19 PHP
PHP 实例化类的一点摘记
2008/03/23 PHP
如何使用PHP对网站验证码进行破解
2015/09/17 PHP
PHP框架laravel的.env文件配置教程
2017/06/07 PHP
使用js在页面中绘制表格核心代码
2013/09/16 Javascript
js实现两点之间画线的方法
2015/05/12 Javascript
vuex中的 mapState,mapGetters,mapActions,mapMutations 的使用
2018/04/13 Javascript
从零开始用electron手撸一个截屏工具的示例代码
2018/10/10 Javascript
JS/HTML5游戏常用算法之碰撞检测 像素检测算法实例详解
2018/12/12 Javascript
[07:43]《辉夜杯》公开赛晋级外卡赛战队—TRG训练生活探秘
2015/12/11 DOTA
[01:03:42]VP vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
python用于url解码和中文解析的小脚本(python url decoder)
2013/08/11 Python
Python获取当前函数名称方法实例分享
2018/01/18 Python
对numpy中轴与维度的理解
2018/04/18 Python
Diango + uwsgi + nginx项目部署的全过程(可外网访问)
2018/04/22 Python
Python实现拷贝/删除文件夹的方法详解
2018/08/29 Python
python的继承知识点总结
2018/12/10 Python
python3+selenium实现qq邮箱登陆并发送邮件功能
2019/01/23 Python
Python Datetime模块和Calendar模块用法实例分析
2019/04/15 Python
PYTHON绘制雷达图代码实例
2019/10/15 Python
Python ORM编程基础示例
2020/02/02 Python
详解css3使用transform出现字体模糊的解决办法
2020/10/16 HTML / CSS
AVON雅芳官网:世界上最大的美容化妆品公司之一
2016/11/02 全球购物
一些Unix笔试题和面试题
2013/01/22 面试题
铁路个人事迹材料
2014/01/30 职场文书
《赵州桥》教学反思
2014/02/17 职场文书
差生评语大全
2014/05/04 职场文书
企业授权委托书范本
2014/09/22 职场文书
2014学习十八届四中全会精神思想汇报范文
2014/10/23 职场文书
会计工作岗位职责
2015/02/03 职场文书
学校清洁工岗位职责
2015/04/15 职场文书
Python办公自动化之教你用Python批量识别发票并录入到Excel表格中
2021/06/26 Python
css3中2D转换之有趣的transform形变效果
2022/02/24 HTML / CSS
python实现商品进销存管理系统
2022/05/30 Python