gitbook/Python自动化办公实战课/docs/348179.md
2022-09-03 22:05:03 +08:00

226 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 10按指定顺序给词语排序提高查找效率
你好,我是尹会生。
之前我在游戏行业工作的时候经常需要取得用户在线时长TOP3、用户战斗力TOP5、用户完成任务数量TOP10等数据相信你在工作中也会有遇到从大量数据取得TopN这类需求。
提取TopN的操作本质上是对大量数据先进行排序然后根据排序的结果取出前N个值。而实现提取TopN的值用Python来实现会非常高效这节课我就来讲一讲具体怎么操作。
## 使用sorted()函数实现排序
在Python中已经内置了排序函数sorted()函数它是Python中实现排序最简单且最直接的形式可以解决80%的排序问题。那么我们就来学习一下怎么用sorted()函数对常见的数据类型进行排序。
先看sorted()函数的定义。
```
sorted(iterable, cmp=None, key=None, reverse=False)
```
sorted函数共有四个参数第一个参数是要排序的对象后面三个参数是排序的方式。
如果要为某个对象排序的话你可以直接将它作为sorted()函数的第一个参数,返回结果会将对象的值进行从小到大的排序。
如果sorted()返回的排序结果不满足你的需要,比如你想要从大到小的排序,那你就可以利用后面三个参数来改变排序的方式,实现自定义排序。
所以今天这节课我会带你学习怎么使用sorted()函数实现默认排序和自定义排序来解决你实际工作中遇到的多种排序问题。我们先来看怎么使用sorted()实现默认排序。
### 默认排序
**sorted()函数的默认排序是按照从小到大的顺序进行排序的**。例如一家公司即将上市需要在职的前1000名员工的工号和姓名工号越小的员工配股越多。这时候就需要给所有员工的工号进行排序并取得工号排在前1000个员工的名字。具体怎么做呢
我们来看sorted()函数的用法。我们从它的第一个参数开始学习。第一个参数是sorted()函数要排序的对象我以列表为例把员工的工号放到列表中我们来看一下sorted()函数是怎么对列表中的工号进行从小到大的排序的。
```
sorted_list = sorted([30, 50, 20, 10, 40])
print(sorted_list)
# [10, 20, 30, 40, 50]
```
我在代码中给sorted()函数指定了一个列表类型的参数“\[30, 50, 20, 10, 40\]”。按照sorted()函数的定义这个列表会被sorted()函数按照数字从小到大进行排序并返回排序后的结果。并且我把结果放入sorted\_list对象中你从代码中可以看到sorted\_list已经把数据按照默认从小到大的顺序排列好并把结果放在一个列表数据类型中。
这就是sorted()的基本用法,其实是比较简单的,不过这里**有三点需要你注意。**
**首先sorted()函数的默认排序方式是从小到大进行排序,那么对于列表中的数字就会按照默认的算法进行大小比较。但是除了数字外,列表中还会出现字母或字母与数字混合的方式,你也需要掌握这两种方式的排序处理方法。**
比如说英文字母会按照列表中字符串的第一个字母从A到Z、从a到z进行排序。如果字符串的第一个字母相同就会比较第二个字母以此类推直到比较完字符串中的所有字符。
由于默认排序的时候不支持同时对英文字母和数字进行排序,那么如果列表中既包含了字母,又包含了数字,在默认排序时程序就会提示一个异常,那就是无法使用“<”比较整数和字符串。
```
TypeError: '<' not supported between instances of 'int' and 'str'
```
因此,当列表中出现类似“\["a", 1, "bb"\]”这种,**既包含字符串又包含数字的列表,进行排序操作的时候**你需要先统一类型把数字使用str()函数转换为字符串类型,再对列表进行排序。我把代码写出来,供你参考:
```
sorted_list2 = sorted(["a", str(1), "bb"])
print(sorted_list2)
```
**其次你需要注意sorted()函数不会对原有的列表进行修改,它会把排序好的结果存入到一个新的列表当中。**
很多人刚开始使用Python时会把sorted()函数和列表自带的函数sort()函数混淆。
它们虽然名字很像,但支持的数据类型、调用的方式以及返回的结果都不同。
要记住sort()函数是列表数据类型自带的所以只能对列表数据类型进行排序不能对其他数据类型排序但sorted()函数可以支持任何可迭代的对象。
在调用方式上sort()函数使用的方式是“列表.sort()”格式这也是调用时和sorted()函数的差别。
还有一个最大的差别是sort()函数会直接修改当前列表这种修改称作“原地修改”并返回一个空值None。而**sorted()函数不会对原有的列表进行修改,它会把排序好的结果存入到一个新的列表当中**。
为了让你更好地区分sorted()函数和sort()函数我把sort()函数的执行结果也提供给你做参考。
```
list3 = ["a", "c", "bb"]
no_value = list3.sort()
print(list3)
# ['a', 'bb', 'c'] 执行结果
print(no_value)
# None 执行结果
```
在代码的第二行是sort()函数的调用格式。在第三行我输出了排序之后的列表并对列表的值进行了重新排序。在第五行我们可以看到no\_value变量的返回值为None也就是sort()函数会对列表进行原地修改并使用None作为返回。
**最后你需要注意sorted()函数能够支持的数据类型非常多**既能支持基础数据类型又能支持Python自带的内置函数。根据它的函数定义第一个参数是“iterable”对象表示只要该对象可迭代sorted()函数就能对它进行排序。
在基本数据类型中,**序列和映射都可以迭代******序列是指列表、元组、字符串这三种基本数据类型的总称映射就是我们使用过的字典。除了基本数据类型之外像是range() 、map()、zip()等内置函数都是可迭代对象因此掌握sorted()函数,可以对以上多种类型和函数进行排序。
掌握了这三点注意事项在使用sorted()为可迭代类型进行排序时就不会出现什么问题了。那接下来我来带你学习一下它的自定义排序功能通过自定义排序可以让sorted()基于不同的数据类型实现更加灵活的排序。
### 自定义排序
自定义排序是在基本排序基础上,能够支持**更多****的****排序方式和更复杂的数据类型。比如说:**
* 在排序方式上,我们通过参数,可以把默认的从小到大的排序改为从大到小。
* 在对数据类型的支持上,像列表中包含元组这种复杂数据类型,可以通过指定元组的任意一列进行排序。
我们先学习自定义排序是怎么支持更多的排序方式的,它的前提是要先更改默认的排序方式。
例如我想要实现列表的从大到小的排序并提取Top 3这一需求我首先需要使用函数的第四个参数reverse参数改变默认的排序方式排序后我需要使用列表的切片方式提取前三个元素。
我先把代码写出来然后再给你详细解释。可以看到我在代码中使用了sorted\_list4作为排序后的结果并对sorted\_list4进行切片从而得到指定下标对应的值。
```
sorted_list4 = sorted([30, 50, 20, 10, 40], reverse=True)
print(sorted_list4[:3])
# [50, 40, 30]
```
在代码中我为sorted()函数增加了参数reverse。reverse参数在定义的时候是sorted()函数的第四个参数由于sorted()函数的第二、三个参数我希望保持默认不需要在调用的时候传入因此reverse参数在调用时就要作为第二个参数来使用。
由于在调用时它的位置和定义的位置不同,那么我必须使用关键字参数 “reverse=True”的形式把reverse指定为sorted()函数的第二个参数。
增加reverse参数之后sorted()执行的结果也和默认结果不同sorted会将默认排序的结果进行反转。而当五个数字的排序结果反转后也就以从大到小的方式进行了输出。
你看利用sorted()函数的reverse参数就能实现TopN场景的排序工作工作中往往需要只得到TopN的结果不需要将全部的排序结果进行输出这时候你可以将列表按照你的工作需要提取其中的某一部分这种操作也被称作列**表的切片。**
假如你想取得sorted\_list4列表的Top3元素可以对列表排序后使用sorted\_list4\[0:3\]的写法提取列表前三个值这种写法的“0”“3”表示列表的下标“:”表示获取下标的范围。因为列表的下标从0开始因此要写作\[0:3\]的形式,也可以使用它的省略形式\[:3\]这样列表切片操作就会从0开始取值。
总结来说通过reverse参数和列表切片我们可以在默认排序的基础上实现基于列表的TopN场景下的排序以及TopN结果的提取。
不过现实场景往往都是比较复杂的,要排序的数据类型除了列表中包含数字和字符串外,你最经常见到的还有对列表中包含元组这种复合类型进行排序,以及对字典的键或值进行排序。接下来我来带你学习一下两种主要类型,一种是列表+元祖的类型,一种是字典类型。
#### 列表+元组的排序方式
列表+元组形成的组合数据类型,适合存放包含多个属性的对象。我给你看个例子,
“姓名、性别、身份证号”和“学校、姓名、学号”这两组数据就适合用元组存储。因为元组存储的值是不可变的,而这些数据填入之后一般不会修改,刚好和元组的特性吻合。
而且,这些用户信息在工作中往往会被大量存储在一起,为了便于对它们进行排序和查找。你还需要将多个元组再保存到列表当中,这样就形成了列表+元组这种组合类型。
对列表+元组的形式进行排序就需要用自定义排序的字段来实现。那就是在默认排序的基础上增加key参数并通过lambda表达式取得元组第三个位置的学号。
**lambda表达式是简化自定义函数的定义和调用而使用的一种语法**使用lambda表达式取得学号的字符串之后sortd()就可以实现对元组按照学号进行排序了。
在这里key没法通过下标对元组排序而必须通过函数取得参数具体的值。这是sorted()函数为了更灵活地实现排序功能,把设置排序关键字这一功能全部开放给用户造成的。事实上,这也是一种典型的通过牺牲易用性来增加灵活性的设计模式。
这一知识点比较难理解,不过不用担心,我在后续课程陆续为你讲解这类函数,直到你能熟练使用它们为止。
到这里我们就了解了sorted()函数的key参数必须使用函数做参数的原因那就继续来看**key参数的lambda表达式是****怎么****简化自定义函数的**。
比如我想取得元组中包含“Jerry”的学号“1003”以及包含“Tom”的元组学号的“1005”可以使用这样一段代码来实现函数定义和调用方式
```
def s(my_tuple):
return my_tuple[2]
sorted(students, key=s)
sorted(students, key=lambda s: s[2])
```
在代码的1-3行、第5行分别是通过自定义函数和lambda表达式实现的提取元组第三个参数的功能。对比来看自定义函数的定义要比lambda表达式复杂。自定义函数的定义和调用部分在Python中要分开编写这也是简单函数更适合用lambda表达式编写的原因。
lambda表达式通常在函数只有一行语句且不需要强调函数名称的时候使用因此它还有一个名字叫做**匿名函数。**它的结构只包含四部分,即**lambda关键字、需要接收的参数、一个冒号****和****对接收参数的处理**,并且它会把处理结果自动返回。
因此在sorted()函数中通过lambda表达式实现按关键字排序会比使用自定义函数排序更加简洁所以当你遇到只有一行语句的函数场景时可以考虑使用lambda表达式替代自定义函数的定义和调用。
#### 字典类型的排序方式
除了列表+元组的复合类型外,我们经常还需要对字典类型进行排序,**字典类型包含键和值******所以排序的时候可以基于键来排序******也可以基于值来排序**。
我们还是以学生信息为例。我把学生的姓名定义为字典的键把学号定义为字典的值。接下来我们看看sorted()是怎么对字典的键和值进行排序的,实现排序的代码如下:
```
student_dict1 = {'Jerry':'1003',
'Tom':'1005',
'Beta':'2001',
'Shuke':'2003'
}
# 输出字典的键和值
print(student_dict1.items())
# dict_items([('Jerry', '1003'), ('Tom', '1005'), ('Beta', '2001'), ('Shuke', '2003')])
# 按照字典的键排序
print(sorted(student_dict1.items(), key=lambda d: d[0]))
# [('Beta', '2001'), ('Jerry', '1003'), ('Shuke', '2003'), ('Tom', '1005')]
# 按照字典的值排序
result = sorted(student_dict1.items(), key=lambda d: d[1])
print(result)
[('Jerry', '1003'), ('Tom', '1005'), ('Beta', '2001'), ('Shuke', '2003')]
# 将结果转换为字典
print(dict(result))
# {'Jerry': '1003', 'Tom': '1005', 'Beta': '2001', 'Shuke': '2003'}
```
在这段代码中我实现了基于字典的键和字典的值进行排序的功能。由于字典是使用键值对的形式存储数据的所以我先通过字典自带的函数items(),把键值对的形式转换成列表+元组的形式。经过转换以后,字典的键就变成了元组的第一个参数,值就变成了元组的第二个参数。
因此在代码中我把参数key的值设置为“lambda d: d\[0\]”这样就取得了元组的第一个元素排序之后就实现了按照字典的键进行排序的需求。相应的使用“lambda d: d\[1\]”,可以取得元组第二个元素的值,就能实现按照字典的值进行排序的需求。
由于排序后的数据类型已经变成了列表+元组形式。所以我们在代码最后可以通过dict()函数把排序的结果再转换为字典,和排序前的数据类型保持一致。
这就是通过sorted()函数对字典进行排序的解决方法,你在工作中或许还会遇到和这节课不一样的数据类型,但是它们的解决思路是相通的。
1. 如果能转换成列表可以采用更改lambda下标的方式实现对指定字段的排序。
2. 如果不能转换成列表可以尝试将复杂的类型中不需要进行排序的部分进行删减简化成列表或字典类型这样就也能使用sorted实现数据的排序功能了。
## 小结
最后我来给你总结一下这节课的核心内容。我们通过sorted()函数实现了列表、列表+元组、字典类型的排序通过排序后的结果你可以快速提取TopN也可以利用你学习过的其他语言的算法实现更复杂的查找。
在sorted()函数中我们通过编写key参数的值使用了lambda表达式替代了简单的函数让我们的程序更加简洁。当你在日后的工作场景中也出现了只有单个语句的函数时也可以考虑采用lambda表达式替代自定义函数增加代码的可读性。
正是因为sorted()功能的强大我们在python中实现排序几乎不需要通过自行编写代码来实现排序不过你在进行海量数据的时候我还想给你提供两条有用的建议。
第一如果对包含海量数据的列表进行排序时建议将列表通过tuple()函数转换为元组,能够让查找效率有较大提升。
第二在Python标准库collections库中提供了OrderedDict扩展数据类型它的特点是对OrderedDict数据类型进行赋值时会自动进行排序。当你需要一个有序字典时可以考虑选择OrderedDict作为数据存储的类型从而避免手工对数据排序。
我也把这节课的相关代码放在了[GitHub](https://github.com/wilsonyin123/python_productivity/blob/main/%E6%96%87%E7%AB%A010%E4%BB%A3%E7%A0%81.zip)上,你可以去学习查找。
## 思考题
在最后我想给你留一道开放性的问题。为什么Python语言不将所有的数据类型都设计成默认有序的即存入数据时自动进行排序欢迎你说出自己的理由。
欢迎把你对问题的思考和想法分享在留言区,我们一起交流讨论。如果课程帮你解决了一些工作上的问题,也欢迎你把课程分享给你的朋友、同事。