如何在Python中使用sorted()和sort()

《如何在Python中使用sorted()和sort()》

点击“蓝字”关注我们

🔻”Python基础知识”

大卫·丰达科夫斯基  著   

18财税3班 李潇潇    译     

日期:2019年5月6日   

一、

使用sorted()函数对值进行排序

1.1

对编号进行排序

1.2

对字符串进行排序

Python排序中遇到的限制和陷阱

2.1

无法对具有不可比数据类型的列表用sorted函数排序

2.2

当你在对字符串进行排序时,注意大小写

使用带反向参数的sorted()函数

带键参数的sorted()函数

使用.Sort()对值进行排序

区分何时使用sorted()函数和何时使用.sort()

结论:如何在Python中进行排序

   

   说明

       所有程序员都必须编写代码来对项目或数据进行排序。排序对于应用程序中的用户体验至关重要,无论是按时间戳对用户的最新活动进行排序,还是按姓氏的字母顺序放置电子邮件收件人列表。Python的排序功能提供了强大的功能,可以在粒度级别进行基本排序或自定义排序。


        在本指南中,您将学习如何在不同的数据结构中对各种类型的数据进行排序、自定义顺序,以及如何使用Python中的两种不同的排序方法进行排序。


学习任务和目标

在本教程结束时, 您将知道如何:

1.在数据结构上实现基本的 Python 排序和排序

2.区分sorted()和.sort()的不同

3.根据独特的要求, 在代码中自定义复杂的排序顺序

在本教程中,您需要:

       对列表和元组以及集合有基本的了解。这些数据结构将在本教程中使用,并且,我们将对其执行一些基本的操作。(注:本教程使用Python3版本,因此,如果您使用的是Python2版本,那么执行后的输出结果可能和本教程中的示例输出略有不同。)

 

在本指南中, 您将学习:

1.如何在不同的数据结构中对各种类型的数据进行排序, 自定义顺序。

2.如何使用 Python 中的两种不同的排序方法。


一、

使用sorted()函数对值进行排序

1.1

对编号进行排序

您可以使用Python中的sorted()对列表进行排序。

在本例中,定义了整数列表, 将sorted作为数字变量进行参数调用.


> > >
>>> numbers = [6931]
>>>sorted(numbers)
[1369]
>>>numbers
[6931]


此代码的输出是一个新的排序列表。当打印原始变量时,初始值保持不变。

此示例显示了sorted()的四个重要特征:

1.     Sorted()函数不需要被定义。它是一个内置的函数, 可在Python的标准安装中使用。

2.     没有其他参数或范围的sorted()函数默认按按升序排序值, 这意味着值从最小到最大。

3.     因为sorted()提供的排完序的输出, 并不会更改原始数值所在位置的值,所以原始变量保持不变。

4.     当调用sorted()时, 它会提供一个有序列表作为返回值。

最后一点意味着sorted()可以在列表中使用,并且可以立即将输出分配给变量。


> > >
>>> numbers = [6931]
>>> numbers_sorted = sorted(numbers)
>>> numbers_sorted
[1369]
>>> numbers
[6931]

在本例中, 现在有一个新的变量numbers_sorted存储了sorted()的输出。您可以通过调用help()来查询sorted()[如help(sorted)]来确认所有这些观察结果。可选参数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 orderand the
    reverse flag can be set to request the result in descending order.

技术细节:

      如果您正在从 Python 2 过渡到Python3, 并且熟悉Python3中和Python2中名称相同的函数, 那你应了解Python3中的一些重要的更改:

1.     Python3的sorted()没有cmp参数。相反,只有key用于引入自定义排序逻辑。

2.     Key和reverse必须作为关键字参数传递, 这与Python2版本的不同在于,在python2中,它们可以作为位置参数传递。

     如果需要将Python2中的cmp函数转换为键函数, 请查看functools.cmp_to_key()。(本教程不会涵盖使用Python 2的任何示例)

sorted()也可以在元组和集合上使用, 和在列表的使用非常相似:


> > >
>>> numbers_tuple = (6931)
>>> numbers_set = {551010}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1369]
>>> numbers_set_sorted
[01510]

注意:即使输入的是一个集合或一个元组, 输出也是以一个列表的形式, 因为按定义来说,sorted返回就是一个新列表。如果需要返回的对象与输入类型匹配, 那么可以将返回的对象强制转换为一个新的类型。如果试图将生成的列表转换回集合,请务必小心,因为按定义来说,集合是无序排列的:


> > >
>>> numbers_tuple = (6931)
>>> numbers_set = {551010}
>>> numbers_tuple_sorted = sorted(numbers_tuple)
>>> numbers_set_sorted = sorted(numbers_set)
>>> numbers_tuple_sorted
[1369]        
>>> numbers_set_sorted
[01510]
>>> tuple(numbers_tuple_sorted)
(1369)
>>> set(numbers_set_sorted)
{01105}

      正如预期的那样, 转换为集合时的numbers_set_sorted的值未按顺序排序。 另一个变量numbers_tuple_sorted保留了排序顺序。


1.2

对字符串进行排序

        str类型的排序类似于其他迭代, 如列表和元组。下面的示例演示显示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 对整数以外的值进行排序时, 可能会出现一些限制和奇怪的行为。

2.1

无法对具有不可比数据类型的列表用sorted函数排序

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

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

        此错误显示了为什么 Python 无法对给定的值进行排序的原因。它试图通过使用小于运算符 (<) 来确定排序顺序中哪个值较低, 从而使值按顺序排列。您可以通过手动比较这两个值来复制此错误:

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

      当您尝试比较两个不可比较的值而不使用sorted()时, 也会引发同样的TypeError。

      如果可以比较列表中的值, 并且不会抛出TypeError, 则可以对列表进行排序。这样可以防止使用本质上不可排序的值对迭代进行排序, 并生成可能没有意义的输出。

      例如, 这个数字1应该在这个词之前吗?但是, 如果可迭代的包含所有数字的整数和字符串的组合, 则可以使用列表推导将它们转换为可比较的数据类型:

> > >
>>> mixed_numbers = [5"1"100"34"]
>>> sorted(mixed_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1in <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]
[5110034]
>>> sorted([int(x) for x in mixed_numbers])
[1534100]

      mixed_numbers中的每个元素都调用int()来将任何字符串的值转换为整数的值。 然后调用sorted(), 可以成功地比较每个元素, 并提供排序的输出。

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

       尽管列表中的元素看起来不同, 但它们都可以转换为布尔值 (True或False), 并使用以s orted()进行相互比较:

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

[False0FalseFalse1]

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

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

>>> false_values = [False001 == 20FalseFalse]
>>> sorted(false_values)
[False00False0FalseFalse]

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



2.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']
>># List comprehension for Unicode Code Point of first letter in each word
>>> [(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 Code Point。 即使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排在最后一个。

使用带反向参数的sorted()函数

       如help(sorted)所示,有一个名为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 = [6931]
>>> sorted(numbers, reverse=False)
[1369]


带键参数的sorted()函数

       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参数的函数时,有两个主要限制:
1.首先,传递给key的函数中必需参数的数量必须为1。
       下面的示例显示了带有两个参数的加法函数的定义。 当该函数用于数字列表中的键时,它会失败,因为它缺少第二个参数。 每次在排序期间调用add()时,它一次只从列表中接收一个元素:

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

2.是与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 1in <module>
ValueError: invalid literal for int() with base 10'four'

      作为str的每个数值可以转换为int,但是’four’不能。 这会导致引发ValueError并解释’four’无法转换为int,因为它无效。
      key功能非常强大,因为几乎任何内置或用户定义的函数都可用于操作输出顺序。
      如果排序要求是按每个字符串中的最后一个字母排序可迭代(如果字母相同,然后使用下一个字母),则可以定义函数,然后在排序中使用。 下面的示例定义了一个函数,该函数反转传递给它的字符串,然后该函数用作key的参数:

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

      这个词[:: - 1] slice语法被经常用于反转字符串。 每个元素都会应用  reverse_word(),排序顺序将基于后向单词中的字符。
      您可以使用key参数中定义的lambda函数,而不是编写独立函数。
lambda是一个匿名函数:
1、必须内联定义
2、没有名字
3、不能包含语句
4、将像函数一样执行
       在下面的示例中,键被定义为没有名称的lambda,lambda采用的参数是x,x [:: - 1]是将对参数执行的操作:

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

在每个元素上调用x [:: - 1]并反转该单词。 然后将反转的输出用于排序,但仍返回原始单词。

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

    如果需求发生变化,并且顺序也应该反转,那么reverse关键字可以与key参数一起使用:

    当您需要基于属性对类对象进行排序时,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()名称虽然相似的,但它们的内置函数有很大的不同。 它们或多或少完成相同的事情,但我们运用help()查看list.sort()时,突出显示了.sort()和sorted()之间最重要的两个区别:

>>> # Python2
Help on method_descriptor:

>>> # Python2
Help on method_descriptor:

sort(...)
    L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;
    cmp(x, y) -> -101

>>> # 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 = [5261]
>># Try to call .sort() like sorted()
>>> sort(values_to_sort)
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
NameError: name 'sort' is not defined

>># Try to use .sort() on a tuple
>>> tuple_val = (5135)
>>> tuple_val.sort()
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
AttributeError: 'tuple' object has no attribute 'sort'

>># Sort the list and assign to new variable
>>> sorted_values = values_to_sort.sort()
>>> print(sorted_values)
None

>># Print original variable
>>> print(values_to_sort)
[1256]

 与此代码示例中的sorted()相比,.sort()操作的方式有一些非常显着的差异:
1、没有.sort()的有序输出,因此对新变量的赋值仅传递None类型。
2、values_to_sort列表已就地更改,并且不以任何方式维护原始变量。这些差异使得.sort()和sorted()绝对不能在代码中互换,如果以错误的方式使用它们,它们会产生意想不到的结果。
     .sort()和sorted()一样具有相同的键和反向可选关键字参数,这些参数产生与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']

在此示例中,lambda用于执行以下操作:
1、将每个短语拆分为单词列表
2、在这种情况下找到第三个元素或单词
3、找到该单词中的第二个字母

区分何时使用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))

      每次跑步者越过终点线时,他们的号码号和他们的总持续时间(以秒为单位)都会添加到runners这个列表中。
       现在,负责处理结果数据的尽职的程序员看到了这个列表,知道前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,他们已经决定每三十七个越过终点线的人将获得一个免费的健身包。在这一点上,程序员开始出汗,因为跑步者名单已被不可逆转地改变。没有办法按照他们完成的顺序恢复原始的跑步者名单,并找到每三十七个人。
       如果您正在处理重要数据,并且甚至可能需要恢复原始数据,那么.sort()不是最佳选择。如果数据是副本,如果它是不重要的工作数据,如果没有人会因为可以检索而失去它,那么.sort()可以是一个很好的选择。或者,可以使用sorted()并使用相同的lambda对runners进行排序:

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

        在这个带有sorted()的场景中,原始的runners列表仍然完好无损并且没有被覆盖。 找到每三十七人越过终点线的即兴要求可以通过与原始值互动来完成:

>>> every_thirtyseventh_runners = runners[::37]

       every_thirtyseventh_runners是通过在runners上使用列表切片语法中的步幅创建的,该步长仍包含跑步者越过终点线的原始顺序。

结论:如何在Python中进行排序

       sort()和sorted()可以准确地提供所需的排序顺序,如果你正确地使用reverse和key可选关键字参数。
       在输出和就地修改时,两者都具有非常不同的特性,因此请确保您考虑将使用.sort()的任何应用程序功能或程序,因为.sort()可以不可撤销地覆盖数据。
      对于寻找排序挑战的狂热Pythonist,尝试在排序中使用更复杂的数据类型:嵌套的iterables。 另外,请随意深入了解内置函数的开源Python代码实现,并阅读Python中使用的称为Timsort的排序算法。


你学会了吗?

关注我们


《如何在Python中使用sorted()和sort()》
《如何在Python中使用sorted()和sort()》
《如何在Python中使用sorted()和sort()》
《如何在Python中使用sorted()和sort()》


原文始发于微信公众号(Python实验中心):如何在Python中使用sorted()和sort()




点赞

发表评论