Python排序傻傻分不清?一文看透sorted与sort用法

Python排序傻傻分不清?一文看透sorted与sort用法

使用sorted()排序值

开始使用Python排序,首先要了解如何对数字数据和字符串数据进行排序。

1. 排序数字型数据

可以使用Python通过sorted()对列表进行排序。比如定义了一个整数列表,然后使用numbers变量作为参数调用sorted():

>>> numbers = [6, 9, 3, 1]
>>> sorted(numbers)
[1, 3, 6, 9]
>>> numbers
[6, 9, 3, 1]

输出是一个新的排序列表,如果打印原始变量时, 原始数字变量numbers未改变,因为sorted()只提供已排序的输出,而不会更改原始值。这 意味着sorted()可以在列表中使用,将输出立即分配给变量:

>>> numbers = [6, 9, 3, 1]
>>> numbers_sorted = sorted(numbers)
>>> numbers_sorted
[1, 3, 6, 9]
>>> numbers
[6, 9, 3, 1]

我们还可以通过调用sorted的help()来确认所有这些观察结果。可选参数key和reverse将在本教程后面介绍:

>>> # Python 3
>>> help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.
    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

像操作列表一样,sorted()也可同样地用于元组和集合:

>>> numbers_tuple = (6, 9, 3, 1)
>>> numbers_set = {5, 5, 10, 1, 0}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1, 3, 6, 9]
>>> numbers_set_sorted
[0, 1, 5, 10]

注意到,即使输入一个集合和一个元组,输出也是一个列表,因为sorted()按定义返回一个新列表。如果需要匹配输入类型,则可以将返回的对象强制转换为新类型。如果尝试将结果列表强制转换回集合,结果将是无序的,因为集合是无序的,如下:

>>> numbers_tuple = (6, 9, 3, 1)
>>> numbers_set = {5, 5, 10, 1, 0}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1, 3, 6, 9]
>>> numbers_set_sorted
[0, 1, 5, 10]
>>> tuple(numbers_tuple_sorted)
(1, 3, 6, 9)
>>> set(numbers_set_sorted)
{0, 1, 10, 5}

2. 排序字符串型数据

字符串类型与其他可迭代对象类似,如列表和元组。下面的示例显示了sorted()如何将传递给它的字符串进行遍历,并在输出中对每个字符进行排序:

>>> string_number_value = '34521'
>>> string_value = 'I like to sort'
>>> sorted_string_number = sorted(string_number_value)
>>> sorted_string = sorted(string_value)
>>> sorted_string_number
['1', '2', '3', '4', '5']
>>> sorted_string
[' ', ' ', ' ', 'I', 'e', 'i', 'k', 'l', 'o', 'o', 'r', 's', 't', 't']

sorted()将字符串视为列表并遍历每个元素。在字符串中,每个元素表示字符串中的一个字符,sorted会以相同的方式处理一个字符串,对每个字符进行排序,包括空格。 .

我们通过使用split()可以改变输出是单个字符的结果,以空格为边界将原始字符串拆分成几个单词,再通过.join()将几个单词重新组合在一起成为新的字符串,具体如下:

>>> string_value = 'I like to sort'
>>> sorted_string = sorted(string_value.split())
>>> sorted_string
['I', 'like', 'sort', 'to']
>>> ' '.join(sorted_string)
'I like sort to'

Python排序的局限性和陷阱

当使用Python对整数值进行排序时,可能会出现一些限制和奇怪的现象。

1. 具有不能比较数据类型的列表无法进行排序

有些数据类型使用sorted是无法进行比较的,因为它们的类型不同。如果尝试在包含不可比较数据的列表上使用sorted(),Python将返回错误。在此示例中,由于不兼容性,无法对同一列表中的None和int进行排序:

>>> mixed_types = [None, 0]
>>> sorted(mixed_types)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'NoneType'

此错误显示了为什么Python无法对给定的值进行排序。它试图通过使用小于运算符(<)来确定值,以确定排序顺序中哪个值较低。 例如,数字1应该出现在苹果这个词之前吗?但是,如果迭代器包含所有数字的整数和字符串的组合,则可以使用列表推导将它们强制转换为可比较的数据类型:

>>> mixed_numbers = [5, "1", 100, "34"]
>>> sorted(mixed_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
>>> # List comprehension to convert all values to integers
>>> [int(x) for x in mixed_numbers]
[5, 1, 100, 34]
>>> sorted([int(x) for x in mixed_numbers])
[1, 5, 34, 100]

mixed_numbers中的每个元素都调用了int()来将任何str值转换为int值。然后调用sorted()并成功比较每个元素并提供排序的输出。

另外,Python还可以隐式地将值转换为另一种类型。在下面的示例中,1 <= 0的评估是false语句,因此评估的输出将为False。数字1可以转换为True作为bool类型,而0转换为False。

即使列表中的元素看起来不同,它们也可以全部转换为布尔值(True或False)并使用sorted()进行相互比较:

>>> similar_values = [False, 0, 1, 'A' == 'B', 1 <= 0]
>>> sorted(similar_values)
[False, 0, False, False, 1]

‘A’==’B’和1 <= 0转换为False并在有序输出中返回。

此示例说明了排序的一个重要方面: 排序稳定性。 在Python中,当你对相等的值进行排序时,它们将在输出中保留其原始顺序。即使1移动,所有其他值都相等,它们保持相对于彼此的原始顺序。在下面的示例中,所有值都被视为相等,并将保留其原始位置:

>>> false_values = [False, 0, 0, 1 == 2, 0, False, False]
>>> sorted(false_values)
[False, 0, 0, False, 0, False, False]

如果检查原始顺序和排序输出,可以看到1 == 2转换为False,所有排序输出都是原始顺序。

2. 当排序字符串时,大小写很重要

sorted()可用于字符串列表,以按升序对值进行排序,默认情况下按字母顺序排列:

>>> names = ['Harry', 'Suzy', 'Al', 'Mark']
>>> sorted(names)
['Al', 'Harry', 'Mark', 'Suzy']

但是,Python使用每个字符串中第一个字母的Unicode代码点来确定升序排序。意思是sorted()不会将名称Al和al视为相同。此示例使用ord()返回每个字符串中第一个字母的Unicode代码点:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case)
['Mark', 'Suzy', 'al', 'harry']
>>> # 每个word中第一个字母的unicode代码点列表推导式
>>> [(ord(name[0]), name[0]) for name in sorted(names_with_case)]
[(77, 'M'), (83, 'S'), (97, 'a'), (104, 'h')]

name [0]返回sorted(names_with_case)的每个元素中的第一个字符,ord()提供Unicode代码点。即使a在字母表中的M之前,M的代码点在a之前,因此排序的输出首先是M。 如果第一个字母相同,则sorted()将使用第二个字符来确定顺序,第三个字符等,依此类推,一直到字符串的结尾:

>>> very_similar_strs = ['hhhhhd', 'hhhhha', 'hhhhhc','hhhhhb']
>>> sorted(very_similar_strs)
['hhhhha', 'hhhhhb', 'hhhhhc', 'hhhhhd']

除最后一个字符外,very_similar_strs的每个值都相同。 sorted()比较字符串,因为前五个字符相同,输出将基于第六个字符。

包含相同值的字符串将最终排序为最短到最长,因为较短的字符串没有要与较长字符串进行比较的元素:

>>> different_lengths = ['hhhh', 'hh', 'hhhhh','h']
>>> sorted(different_lengths)
['h', 'hh', 'hhhh', 'hhhhh']

最短的字符串h排序第一,最长的字符串hhhhh排序最后。

用reverse参数使用sorted()

如sorted()的help()文档所示,有一个名为reverse的可选关键字参数,它将根据分配给它的布尔值更改排序行为。如果将reverse指定为True,则排序将按降序排列:

>>> names = ['Harry', 'Suzy', 'Al', 'Mark']
>>> sorted(names)
['Al', 'Harry', 'Mark', 'Suzy']
>>> sorted(names, reverse=True)
['Suzy', 'Mark', 'Harry', 'Al']

排序逻辑保持不变,这意味着名称仍按其第一个字母排序。但是,如果reverse关键字设置为True,则输出反转。

如果指定了False,则排序将保持升序。可以使用前面的任何示例来使用True或False来查看reverse的行为:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case, reverse=True)
['harry', 'al', 'Suzy', 'Mark']
>>> similar_values = [False, 1, 'A' == 'B', 1 <= 0]
>>> sorted(similar_values, reverse=True)
[1, False, False, False]
>>> numbers = [6, 9, 3, 1]
>>> sorted(numbers, reverse=False)
[1, 3, 6, 9]

sorted()使用key参数排序

sorted()最强大的功能之一是一个叫做key的关键字参数。此参数需要将函数传递给它,并且该函数将用于要排序的列表中的每个值,以确定生成的顺序。

我们假设排序一个特定列表的要求是列表中字符串的长度,最短到最长。返回字符串长度len()的函数将与key参数一起使用:

>>> word = 'paper'
>>> len(word)
5
>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=len)
['pie', 'book', 'banana', 'Washington']

生成的顺序是一个字符串顺序最短到最长的列表。列表中每个元素的长度由len确定,然后以升序返回。

回到前面的例子,当大小写不同时按第一个字母排序。key可以通过将整个字符串转换为小写来解决该问题:

>>> names_with_case = ['harry', 'Suzy', 'al', 'Mark']
>>> sorted(names_with_case)
['Mark', 'Suzy', 'al', 'harry']
>>> sorted(names_with_case, key=str.lower)
['al', 'harry', 'Mark', 'Suzy']

输出值尚未转换为小写,因为key不会处理原始列表中的数据。在排序期间,传递给key的函数将在每个元素上调用以确定排序顺序,但原始值仍将体现在输出中。 使用带有key参数的函数时,有两个主要限制。

首先,传递给key的函数中参数的数量必须为1。

下面的示例显示了带有两个参数的加法函数的定义。当该函数用于数字列表中的键时,它会失败,因为它缺少第二个参数。每次在排序期间调用add()时,它一次只从列表中接收一个元素:

>>> def add(x, y):
...     return x + y
... 
>>> values_to_add = [1, 2, 3]
>>> sorted(values_to_add, key=add)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() missing 1 required positional argument: 'y'

第二个限制是与key一起使用的函数必须能够处理iterable中的所有值。 例如,有一个数字列表,表示为要在sorted中使用的字符串,而key将尝试将它们转换为使用int。如果iterable中的值不能转换为整数,则该函数将失败:

>>> values_to_cast = ['1', '2', '3', 'four']
>>> sorted(values_to_cast, key=int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'four'

作为字符串的每个数值都可以转换为int,但是four不能。这会导致引发ValueError并解释four无法转换为int,因为它无效。

key功能非常强大,因为几乎任何内置或用户定义的函数都可用于操作输出顺序。

如果排序要求是按每个字符串中的最后一个字母排序可迭代(如果字母相同,然后使用下一个字母),则可以定义函数,然后在排序中使用。下面的示例定义了一个函数,该函数反转传递给它的字符串,然后该函数用作键的参数:

>>> def reverse_word(word):
...     return word[::-1]
...
>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=reverse_word)
['banana', 'pie', 'book', 'Washington']

word[::-1]切片语法用于反转字符串。每个元素都会应用reverse_word(),排序顺序将基于后向单词中的字符。

当然,也可以使用key参数中定义的lambda函数,而不是编写独立函数。 lambda匿名函数:1)必须内联定义;2)没有名字;3)不能包含statement;4)像函数一样执行。

在下面的示例中,key被定义为没有名称的lambda,lambda采用的参数是x,然后x [:: -1]是将对参数执行的操作:

>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=lambda x: x[::-1])
['banana', 'pie', 'book', 'Washington']

在每个元素上调用x [::-1]并反转该单词。然后将反转的输出用于排序,但仍返回原始单词。 如果需求发生变化,要求顺序也应该反转,那么reverse关键字可以与key参数一起使用:

>>> words = ['banana', 'pie', 'Washington', 'book']
>>> sorted(words, key=lambda x: x[::-1], reverse=True)
['Washington', 'book', 'pie', 'banana']

当需要基于属性对类对象进行排序时,lambda函数也很有用。如果有一组学生并需要按最终成绩(从最高到最低)对其进行排序,则可以使用lambda从该课程中获取成绩属性:

>>> from collections import namedtuple
>>> StudentFinal = namedtuple('StudentFinal', 'name grade')
>>> bill = StudentFinal('Bill', 90)
>>> patty = StudentFinal('Patty', 94)
>>> bart = StudentFinal('Bart', 89)
>>> students = [bill, patty, bart]
>>> sorted(students, key=lambda x: getattr(x, 'grade'), reverse=True)
[StudentFinal(name='Patty', grade=94), StudentFinal(name='Bill', grade=90), StudentFinal(name='Bart', grade=89)]

此示例使用namedtuple生成具有name和grade属性的类。 lambda在每个元素上调用getattr()并返回grade的值。 reverse设置为True以使升序输出转为降序,以便首先排序最高等级。

当在sorted()上同时使用key和reverse关键字参数时,如何进行排序的可能性是无穷无尽的。当对一个小函数使用基本lambda时,代码可以保持干净和简短,或者可以编写一个全新的函数导入,并在key参数中使用它。

使用.sort()排序值

名称相似的.sort()与sorted()内置函数有着很大的不同。虽然它们或多或少都可以完成相同的事情,但list.sort()的help()文档突出显示了.sort()和sorted()之间最重要的两个区别:

>>> # Python2
Help on method_descriptor:
sort(...)
    L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
    cmp(x, y) -> -1, 0, 1
>>> # Python3
>>> help(list.sort)
Help on method_descriptor:
sort(...)
    L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*

首先,sort是列表类的一种方法,只能与列表一起使用。它不是传递给它的迭代的内置函数。其次,sort返回None并修改值。我们来看看代码中这两种差异的影响:

>>> values_to_sort = [5, 2, 6, 1]
>>> # 尝试调用像使用sorted()调用sort()
>>> sort(values_to_sort)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'sort' is not defined
>>> # 尝试在一个元组上使用 .sort()
>>> tuple_val = (5, 1, 3, 5)
>>> tuple_val.sort()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'sort'
>>> # 排序列表并且赋值给新的变量
>>> sorted_values = values_to_sort.sort()
>>> print(sorted_values)
None
>>> # 打印原始变量
>>> print(values_to_sort)
[1, 2, 5, 6]

与此代码示例中的sorted()相比,sort()操作的方式有一些非常显着的差异:

\1. sort()不产生有序输出,因此对新变量的赋值仅传递None类型。

\2. values_to_sort列表已就地更改,并且不以任何方式维持原始顺序。

这些差异使得.sort()和sorted()绝对不能在代码中互换,如果以错误的方式使用它们,它们会产生意想不到的结果。

.sort()具有相同的key 和reverse可选关键字参数,这些参数产生与sorted()相同的强大功能。在这里,可以按第三个单词的第二个字母对短语列表进行排序,然后反向返回列表:

>>> phrases = ['when in rome', 
...     'what goes around comes around', 
...     'all is fair in love and war'
...     ]
>>> phrases.sort(key=lambda x: x.split()[2][1], reverse=True)
>>> phrases
['what goes around comes around', 'when in rome', 'all is fair in love and war']

何时使用sorted和.sort?

我们已经看到了sorted()和.sort()之间的区别,但我们什么时候使用?该使用哪个?

假设有一场5k比赛即将举行:第一届年度Python 5k。需要获取和分类来自比赛的数据,参赛者的号码和完成比赛所需的秒数:

>>> from collections import namedtuple
>>> Runner = namedtuple('Runner', 'bibnumber duration')

当参赛者越过终点线时,每个参赛者将被添加到名为参赛者的列表中。在5k比赛中,并非所有参赛者同时越过起跑线,所以第一个越过终点线的人可能实际上不是最快的人:

>>> runners = []
>>> runners.append(Runner('2528567', 1500))
>>> runners.append(Runner('7575234', 1420))
>>> runners.append(Runner('2666234', 1600))
>>> runners.append(Runner('2425234', 1490))
>>> runners.append(Runner('1235234', 1620))
>>> # Thousands and Thousands of entries later...
>>> runners.append(Runner('2526674', 1906))

每次参赛者越过终点线时,他们的号码号和他们的总持续时间(以秒为单位)都会添加到跑步者。

现在,负责处理结果数据的尽职程序员看到了这个列表,知道前5名最快的参与者是获得奖品的获胜者,剩下的参赛者将按最快的时间进行排序。

赛事中没有提到通过不同属性进行多类型的排序要求,也没有提到将列表在某处存储,只需按持续时间排序并获取持续时间最短的五个参与者:

>>> runners.sort(key=lambda x: getattr(x, 'duration'))
>>> top_five_runners = runners[:5]

程序员选择在key参数中使用lambda来获取每个参赛者的duration属性,并使用.sort()对运行程序进行排序。在对参赛者进行排序后,前5个元素存储在top_five_runners中。

比赛总监过来告诉程序员,由于目前发布的Python是3.7,他们决定每隔37位越过终点线的人将获得一个免费的健身包。这时候,程序员开始出汗,因为参赛者名单已被不可逆转地改变。没有办法按照他们完成的顺序恢复原始的参赛者名单,并找到这些人。

如果你正在处理重要数据,甚至可能需要恢复原始数据,那么.sort()不是最佳选择。相反,如果数据是副本,是不重要的工作数据,或者没有人会在意失不失去它,那么.sort()可以是一个很好的选择。

因此,可以用sorted(),使用相同的lambda对runners进行排序:

>>> runners_by_duration = sorted(runners, key=lambda x: getattr(x, 'duration'))
>>> top_five_runners = runners_by_duration[:5]
>>> every_thirtyseventh_runners = runners[::37]
Python教程

Python小白必学的面向对象

2021-4-27 8:35:05

Python教程

太全了!Python3常用内置函数总结

2021-4-27 8:35:29

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索