ZetCode

Python 列表排序

最后修改于 2024 年 9 月 16 日

在本文中,我们将介绍如何在 Python 语言中对列表元素进行排序。

排序

在计算机科学中,排序是指将元素按有序序列排列。多年来,人们开发了多种算法来对数据进行排序,包括归并排序、快速排序、选择排序或冒泡排序。(排序的另一种含义是分类,即将具有相似属性的元素分组。)

排序的相反操作,即将元素序列重新排列成随机或无意义的顺序,称为打乱

数据可以按字母顺序或数字顺序排序。排序键指定了执行排序所依据的标准。可以按多个键对对象进行排序。例如,在对用户进行排序时,可以将用户的姓名作为主要排序键,职业作为次要排序键。

排序顺序

标准顺序称为升序:a 到 z,0 到 9。反向顺序称为降序:z 到 a,9 到 0。对于日期和时间,升序表示较早的值排在较晚的值之前,例如 1/1/2020 会排在 1/1/2021 之前。

稳定排序

稳定排序是指在排序过程中保持相等元素的初始相对顺序。有些排序算法天然稳定,有些则不稳定。例如,归并排序和冒泡排序是稳定的排序算法。另一方面,堆排序和快速排序是不稳定排序算法的示例。

考虑以下值:3715593。稳定排序的结果如下:1335579。值 3 和 5 的顺序得以保留。不稳定排序可能产生以下结果:1335579

Python 使用 timsort 算法。这是一种混合稳定排序算法,源自归并排序和插入排序。它由 Tim Peters 于 2002 年为 Python 编程语言实现。

Python 排序函数

Python 有两个基本的列表排序函数:sortsortedsort 会就地对列表进行排序,而 sorted 则从可迭代对象中的项返回一个新的排序后的列表。两个函数具有相同的选项:keyreversekey 接受一个函数,该函数将应用于正在排序的列表中的每个值,以确定排序结果。reverse 选项可以反转比较顺序。

这两个函数都产生稳定的排序。

Python 列表就地排序

列表容器的 sort 函数在排序时会修改原始列表。

inplace_sort.py
#!/usr/bin/python

words = ['forest', 'wood', 'tool', 'arc', 'sky', 'poor', 'cloud', 'rock']
vals = [2, 1, 0, 3, 4, 6, 5, 7]

words.sort()
print(words)

vals.sort()
print(vals)

在此示例中,我们对字符串和整数列表进行排序。原始列表被修改了。

$ ./inplace_sort.py
['arc', 'cloud', 'forest', 'poor', 'rock', 'sky', 'tool', 'wood']
[0, 1, 2, 3, 4, 5, 6, 7]

Python sorted 示例

sorted 函数不会修改原始列表;相反,它会创建一个新的修改后的列表。

sorted_fun.py
#!/usr/bin/python

words = ['forest', 'wood', 'brisk', 'tree', 'sky', 'cloud', 'rock', 'falcon']

sorted_words = sorted(words)
print('Original:', words)
print('Sorted:', sorted_words)

此示例从原始列表中创建了一个新的排序后的单词列表,而原始列表保持不变。

$ ./sorted_fun.py
Original: ['forest', 'wood', 'brisk', 'tree', 'sky', 'cloud', 'rock', 'falcon']
Sorted: ['brisk', 'cloud', 'falcon', 'forest', 'rock', 'sky', 'tree', 'wood']

Python 列表按升序/降序排序

升序/降序由 reverse 选项控制

asc_desc.py
#!/usr/bin/python

words = ['forest', 'wood', 'tool', 'arc', 'sky', 'poor', 'cloud', 'rock']

words.sort()
print(words)

words.sort(reverse=True)
print(words)

此示例按升序和降序对单词列表进行排序。

$ ./asc_desc.py
['arc', 'cloud', 'forest', 'poor', 'rock', 'sky', 'tool', 'wood']
['wood', 'tool', 'sky', 'rock', 'poor', 'forest', 'cloud', 'arc']

Python 日期列表排序

在下一个示例中,我们将对日期列表进行排序。

sort_date.py
#!/usr/bin/python

from datetime import datetime


values = ['8-Nov-19', '21-Jun-16', '1-Nov-18', '7-Apr-19']
values.sort(key=lambda d: datetime.strptime(d, "%d-%b-%y"))

print(values)

匿名函数使用 strptime 函数,该函数根据给定的字符串创建一个 datetime 对象。实际上,sort 函数对 datetime 对象进行排序。

如果您不熟悉 lambda 关键字,请在 Python lambda 教程中了解更多关于匿名函数的信息。

$. /sort_date.py
['21-Jun-16', '1-Nov-18', '7-Apr-19', '8-Nov-19']

Python 按元素索引排序列表

Python 列表可以包含嵌套的可迭代对象。在这种情况下,我们可以选择要排序的元素。

sort_elem_idx.py
#!/usr/bin/python

vals = [(4, 0), (0, -2), (3, 5), (1, 1), (-1, 3)]

vals.sort()
print(vals)

vals.sort(key=lambda e: e[1])
print(vals)

此示例首先按第一个元素对嵌套的元组进行排序,然后按第二个元素进行排序。

vals.sort(key=lambda e: e[1])

通过提供一个返回元组第二个元素的匿名函数,我们可以按第二个值对元组进行排序。

$ ./sort_elem_idx.py
[(-1, 3), (0, -2), (1, 1), (3, 5), (4, 0)]
[(0, -2), (4, 0), (1, 1), (-1, 3), (3, 5)]

Python 按嵌套列表的总和排序列表

假设我们有嵌套列表,它们都有不同的排名。最终排名是所有值的总和。

sort_sum.py
#!/usr/bin/python

data = [[10, 11, 12, 13], [9, 10, 11, 12], [8, 9, 10, 11], [10, 9, 8, 7],
    [6, 7, 8, 9], [5, 5, 5, 1], [5, 5, 5, 5], [3, 4, 5, 6], [10, 1, 1, 2]]

data.sort()
print(data)

data.sort(key=sum)
print(data)

默认情况下,排序函数按嵌套列表的第一个值进行排序。为了达到我们的目标,我们将内置的 sum 函数传递给 key 选项。

$ ./sort_sum.py
[[3, 4, 5, 6], [5, 5, 5, 1], [5, 5, 5, 5], [6, 7, 8, 9], [8, 9, 10, 11], [9, 10, 11, 12], [10, 1, 1, 2], [10, 9, 8, 7], [10, 11, 12, 13]]
[[10, 1, 1, 2], [5, 5, 5, 1], [3, 4, 5, 6], [5, 5, 5, 5], [6, 7, 8, 9], [10, 9, 8, 7], [8, 9, 10, 11], [9, 10, 11, 12], [10, 11, 12, 13]]

此示例显示了默认排序和自定义排序。

Python 按本地化字符串排序列表

对于本地化感知排序,我们可以使用 locale.strxfrm 作为键函数。

locale_sort.py
import locale

words = ['zem', 'čučoriedka', 'drevo', 'hrozno', 'hora', 'džem', 'element',
         'štebot', 'cesta', 'černice', 'ďateľ', 'rum', 'železo', 'prameň', 'sob',
         'chobot', 'chmel', 'cmar', 'džús', 'dzekať']

locale.setlocale(locale.LC_COLLATE, ('sk_SK', 'UTF8'))

words.sort(key=locale.strxfrm)

for word in words:
    print(word)

此示例对斯洛伐克语单词进行排序。

$ ./locale_sort.py
cesta
cesta
cmar
černice
čučoriedka
ďateľ
drevo
dzekať
džem
džús
element
hora
hrozno
chmel
chobot
prameň
rum
sob
štebot
zem
železo
注意:斯洛伐克语单词的排序结果不完全正确。字母 ď 应该在 d 之后。这取决于语言支持的程度。

Python 字典列表排序

在对字典进行排序时,我们可以选择排序所依据的属性。

sort_dict.py
#!/usr/bin/python

users = [
  {'name': 'John Doe', 'date_of_birth': 1987},
  {'name': 'Jane Doe', 'date_of_birth': 1996},
  {'name': 'Robert Brown', 'date_of_birth': 1977},
  {'name': 'Lucia Smith', 'date_of_birth': 2002},
  {'name': 'Patrick Dempsey', 'date_of_birth': 1994}
]

users.sort(reverse=True, key=lambda e: e['date_of_birth'])

for user in users:
    print(user)

我们有一个用户列表。每个用户由一个字典表示。

users.sort(reverse=True, key=lambda e: e['date_of_birth'])

在匿名函数中,我们选择 date_of_birth 属性。

$ ./sort_dict.py
{'name': 'Lucia Smith', 'date_of_birth': 2002}
{'name': 'Jane Doe', 'date_of_birth': 1996}
{'name': 'Patrick Dempsey', 'date_of_birth': 1994}
{'name': 'John Doe', 'date_of_birth': 1987}
{'name': 'Robert Brown', 'date_of_birth': 1977}

用户按出生日期降序排序。

Python 成绩列表排序

世界各地存在各种评分系统。我们的示例包含 A+ 或 C- 等成绩,这些成绩无法按字典顺序排列。我们使用一个字典,其中每个成绩都有其指定的值。

grades.py
#!/usr/bin/python

data = 'A+ A A- B+ B B- C+ C C- D+ D'
grades = { grade: idx for idx, grade in enumerate(data.split()) }

def mc(e):
    return grades.get(e[1])

students = [('Anna', 'A+'), ('Jozef', 'B'), ('Rebecca', 'B-'), ('Michael', 'D+'),
    ('Zoltan', 'A-'), ('Jan', 'A'), ('Michelle', 'C-'), ('Sofia', 'C+')]

print(grades)

students.sort(key=mc)
print(students)


# from operator import itemgetter
# students.sort(key=lambda e: itemgetter(e[1])(grades))

我们有一个学生列表。每个学生都有一个姓名和成绩,以嵌套元组的形式表示。

data = 'A+ A A- B+ B B- C+ C C- D+ D'
grades = { grade: idx for idx, grade in enumerate(data.split()) }

我们构建了成绩字典。每个成绩都有其值。成绩将按其字典值排序。

def mc(e):
    return grades.get(e[1])

键函数仅返回成绩的值。

# from operator import itemgetter
# students.sort(key=lambda e: itemgetter(e[1])(grades))

此解决方案使用了匿名函数。

$ ./grades.py
{'A+': 0, 'A': 1, 'A-': 2, 'B+': 3, 'B': 4, 'B-': 5, 'C+': 6, 'C': 7, 'C-': 8, 'D+': 9, 'D': 10}
[('Anna', 'A+'), ('Jan', 'A'), ('Zoltan', 'A-'), ('Jozef', 'B'), ('Rebecca', 'B-'), ('Sofia', 'C+'), ('Michelle', 'C-'), ('Michael', 'D+')]

Python 按字符串长度排序列表

有时,我们需要按字符串长度对字符串进行排序。

sort_by_len.py
#!/usr/bin/python

def w_len(e):
  return len(e)

words = ['forest', 'wood', 'tool', 'sky', 'poor', 'cloud', 'rock', 'if']

words.sort(reverse=True, key=w_len)

print(words)

在此示例中,我们不使用匿名函数。

def w_len(e):
  return len(e)

w_len 函数返回每个元素的长度。

$ ./sort_by_len.py
['forest', 'cloud', 'wood', 'tool', 'poor', 'rock', 'sky', 'if']

单词按长度降序排列。

Python 按大小写排序列表

默认情况下,首字母大写的字符串会排在其他字符串之前。我们也可以不区分大小写地对字符串进行排序。

case_sorting.py
#!/usr/bin/python

text = 'Today is a beautiful day. Andy went fishing.'
words = text.replace('.', '')

sorted_words = sorted(words.split(), key=str.lower)
print('Case insensitive:', sorted_words)

sorted_words2 = sorted(words.split())
print('Case sensitive:', sorted_words2)

通过将 str.lower 函数传递给 key 属性,我们可以执行不区分大小写的排序。

$ ./case_sorting.py
Case insensitive: ['a', 'Andy', 'beautiful', 'day', 'fishing', 'is', 'Today', 'went']
Case sensitive: ['Andy', 'Today', 'a', 'beautiful', 'day', 'fishing', 'is', 'went']

Python 按姓氏排序列表

在下面的示例中,我们将按姓氏对姓名进行排序。

sort_by_lastname.py
#!/usr/bin/python

names = ['John Doe', 'Jane Doe', 'Robert Brown', 'Robert Novak',
    'Lucia Smith', 'Patrick Dempsey', 'George Marshall', 'Alan Brooke',
    'Harold Andras', 'Albert Doe']

names.sort()
names.sort(key=lambda e: e.split()[-1])


for name in names:
    print(name)

我们有一个姓名列表。每个姓名都包含名和姓。此外,还有几个用户具有相同的姓氏。在这种情况下,我们希望他们按名字排序。

names.sort()
names.sort(key=lambda e: e.split()[-1])

首先,我们按名字对姓名进行排序。然后,我们按姓氏对姓名进行排序。为此,我们分割每个字符串并选择最后一个字符串(索引为 -1)。由于 Python 的排序算法是稳定的,因此会记住第一次排序,并得到预期的输出。

$ ./sort_by_lastname.py
Harold Andras
Alan Brooke
Robert Brown
Patrick Dempsey
Albert Doe
Jane Doe
John Doe
George Marshall
Robert Novak
Lucia Smith

姓名按姓氏排序。Doe 用户按名字正确排序。

Python 排序命名元组列表

在下一个示例中,我们将对命名元组进行排序。

namedtuple_sort.py
#!/usr/bin/python

from typing import NamedTuple


class City(NamedTuple):
    id: int
    name: str
    population: int


c1 = City(1, 'Bratislava', 432000)
c2 = City(2, 'Budapest', 1759000)
c3 = City(3, 'Prague', 1280000)
c4 = City(4, 'Warsaw', 1748000)
c5 = City(5, 'Los Angeles', 3971000)
c6 = City(6, 'Edinburgh', 464000)
c7 = City(7, 'Berlin', 3671000)

cities = [c1, c2, c3, c4, c5, c6, c7]

cities.sort(key=lambda e: e.name)

for city in cities:
    print(city)

City 命名元组有三个属性:idnamepopulation。此示例按名称对命名元组进行排序。

cities.sort(key=lambda e: e.name)

匿名函数返回命名元组的 name 属性。

$ ./namedtuple_sort.py
City(id=7, name='Berlin', population=3671000)
City(id=1, name='Bratislava', population=432000)
City(id=2, name='Budapest', population=1759000)
City(id=6, name='Edinburgh', population=464000)
City(id=5, name='Los Angeles', population=3971000)
City(id=3, name='Prague', population=1280000)
City(id=4, name='Warsaw', population=1748000)

itemgetter 和 attrgetter 函数

Python 提供了 itemgetterattrgetter 便利函数,以使访问函数更轻松、更快速。它们位于 operator 模块中。

helpers.py
#!/usr/bin/python

from typing import NamedTuple
from operator import itemgetter, attrgetter

class City(NamedTuple):
    id: int
    name: str
    population: int

c1 = City(1, 'Bratislava', 432000)
c2 = City(2, 'Budapest', 1759000)
c3 = City(3, 'Prague', 1280000)
c4 = City(4, 'Warsaw', 1748000)
c5 = City(5, 'Los Angeles', 3971000)
c6 = City(6, 'Edinburgh', 464000)
c7 = City(7, 'Berlin', 3671000)

cities = [c1, c2, c3, c4, c5, c6, c7]

sorted_cities = sorted(cities, key=attrgetter('name'))

for city in sorted_cities:
    print(city)

print('---------------------')

sorted_cities = sorted(cities, key=itemgetter(2))

for city in sorted_cities:
    print(city)

我们使用 sorted 和这些辅助函数对城市列表进行排序。

sorted_cities = sorted(cities, key=attrgetter('name'))

我们传递用于排序城市的属性名称。

sorted_cities = sorted(cities, key=itemgetter(2))

对于 itemgetter,我们传递属性的索引。

$ ./named_tuple_sort.py
City(id=7, name='Berlin', population=3671000)
City(id=1, name='Bratislava', population=432000)
City(id=2, name='Budapest', population=1759000)
City(id=6, name='Edinburgh', population=464000)
City(id=5, name='Los Angeles', population=3971000)
City(id=3, name='Prague', population=1280000)
City(id=4, name='Warsaw', population=1748000)
---------------------
City(id=1, name='Bratislava', population=432000)
City(id=6, name='Edinburgh', population=464000)
City(id=3, name='Prague', population=1280000)
City(id=4, name='Warsaw', population=1748000)
City(id=2, name='Budapest', population=1759000)
City(id=7, name='Berlin', population=3671000)
City(id=5, name='Los Angeles', population=3971000)

Python 按多个排序标准排序列表

以下示例按两个排序标准对学生列表进行排序。

multi_sort.py
#!/usr/bin/python

from typing import NamedTuple

class Student(NamedTuple):
    id: int
    name: str
    grade: str
    age: int

s1 = Student(1, 'Patrick', 'A', 21)
s2 = Student(2, 'Lucia', 'B', 19)
s3 = Student(3, 'Robert', 'C', 19)
s4 = Student(4, 'Monika', 'A', 22)
s5 = Student(5, 'Thomas', 'D', 20)
s6 = Student(6, 'Petra', 'B', 18)
s6 = Student(7, 'Sofia', 'A', 18)
s7 = Student(8, 'Harold', 'E', 22)
s8 = Student(9, 'Arnold', 'B', 23)

students = [s1, s2, s3, s4, s5, s6, s7, s8]
students.sort(key=lambda s: (s.grade, s.age))

for student in students:
    print(student)

我们按成绩然后按年龄对学生进行排序。排序是升序的。

students.sort(key=lambda s: (s.grade, s.age))

要进行排序,我们将 lambda 函数传递一个排序属性的元组。

$ ./multiple_sort.py
Student(id=7, name='Sofia', grade='A', age=18)
Student(id=1, name='Patrick', grade='A', age=21)
Student(id=4, name='Monika', grade='A', age=22)
Student(id=2, name='Lucia', grade='B', age=19)
Student(id=9, name='Arnold', grade='B', age=23)
Student(id=3, name='Robert', grade='C', age=19)
Student(id=5, name='Thomas', grade='D', age=20)
Student(id=8, name='Harold', grade='E', age=22)

我们可能希望按多个标准对数据进行排序,并具有不同的排序类型。

第一个解决方案是将键包装在一个类中,该类定义了排序类型。

multi_sort2.py
#!/usr/bin/python

from typing import NamedTuple

class negate:
    def __init__(self, obj):
        self.obj = obj

    def __eq__(self, other):
        return other.obj == self.obj

    def __lt__(self, other):
        return other.obj < self.obj


class Student(NamedTuple):
    id: int
    name: str
    grade: str
    age: int


s1 = Student(1, 'Patrick', 'A', 21)
s2 = Student(2, 'Lucia', 'B', 19)
s3 = Student(3, 'Robert', 'C', 19)
s4 = Student(4, 'Monika', 'A', 22)
s5 = Student(5, 'Thomas', 'D', 20)
s6 = Student(6, 'Petra', 'B', 18)
s6 = Student(7, 'Sofia', 'A', 18)
s7 = Student(8, 'Harold', 'E', 22)
s8 = Student(9, 'Arnold', 'B', 23)

students = [s1, s2, s3, s4, s5, s6, s7, s8]
students.sort(key=lambda s: (s.grade, negate(s.age)))

for student in students:
    print(student)

此示例按成绩升序排序学生,然后按年龄降序排序。

students.sort(key=lambda s: (s.grade, negate(s.age)))

第二个键被 negate 包装。

$ ./multi_sort2.py
Student(id=4, name='Monika', grade='A', age=22)
Student(id=1, name='Patrick', grade='A', age=21)
Student(id=7, name='Sofia', grade='A', age=18)
Student(id=9, name='Arnold', grade='B', age=23)
Student(id=2, name='Lucia', grade='B', age=19)
Student(id=3, name='Robert', grade='C', age=19)
Student(id=5, name='Thomas', grade='D', age=20)

另一种解决方案是两次排序列表。

multi_sort3.py
#!/usr/bin/python

from typing import NamedTuple
from operator import attrgetter

def multi_sort(data, specs):

    for key, reverse in reversed(specs):
        data.sort(key=attrgetter(key), reverse=reverse)
    return data


class Student(NamedTuple):
    id: int
    name: str
    grade: str
    age: int


s1 = Student(1, 'Patrick', 'A', 21)
s2 = Student(2, 'Lucia', 'B', 19)
s3 = Student(3, 'Robert', 'C', 19)
s4 = Student(4, 'Monika', 'A', 22)
s5 = Student(5, 'Thomas', 'D', 20)
s6 = Student(6, 'Petra', 'B', 18)
s6 = Student(7, 'Sofia', 'A', 18)
s7 = Student(8, 'Harold', 'E', 22)
s8 = Student(9, 'Arnold', 'B', 23)

students = [s1, s2, s3, s4, s5, s6, s7, s8]

multi_sort(students, (('grade', False), ('age', True)))

for student in students:
    print(student)

首先,学生按成绩升序排序,然后按年龄降序排序。

def multi_sort(data, specs):

    for key, reverse in reversed(specs):
        data.sort(key=attrgetter(key), reverse=reverse)
    return data

multi_sort 函数将所有排序规范应用于列表。

$ ./multi_sort3.py
Student(id=4, name='Monika', grade='A', age=22)
Student(id=1, name='Patrick', grade='A', age=21)
Student(id=7, name='Sofia', grade='A', age=18)
Student(id=9, name='Arnold', grade='B', age=23)
Student(id=2, name='Lucia', grade='B', age=19)
Student(id=3, name='Robert', grade='C', age=19)
Student(id=5, name='Thomas', grade='D', age=20)
Student(id=8, name='Harold', grade='E', age=22)

扑克牌排序

以下示例对一副扑克牌进行排序。

main.py
import random
from itertools import groupby


def create_deck():

    signs = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
    symbols = ['♠', '♥', '♦', '♣']  # spades, hearts, diamonds, clubs

    deck = [f'{si}{sy}' for si in signs for sy in symbols]

    return deck


def by_poker_order(card):

    poker_order = "2 3 4 5 6 7 8 9 10 J Q K A"

    return poker_order.index(card[:-1])


def by_suit(card):

    return card[-1]


deck = create_deck()
random.shuffle(deck)

# Sort by poker order and then by suit
deck.sort(key=by_poker_order)
deck.sort(key=by_suit)

for k, g in groupby(deck, key=lambda c: c[-1]):
    print(k, list(g))

代码示例创建一副牌。它按花色对牌进行分组并排序。

def create_deck():

    signs = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A']
    symbols = ['♠', '♥', '♦', '♣']  # spades, hearts, diamonds, clubs

    deck = [f'{si}{sy}' for si in signs for sy in symbols]

    return deck

create_deck 方法创建一副扑克牌。有十三种点数和四种花色。

def by_poker_order(card):

    poker_order = "2 3 4 5 6 7 8 9 10 J Q K A"

    return poker_order.index(card[:-1])

该方法返回牌的点数索引。这用于对牌进行排序。

def by_suit(card):

    return card[-1]

by_suit 方法用于按花色对牌进行排序。它返回牌组中的牌的花色字符;它是最后一个字符。

random.shuffle(deck)

使用 random.shuffle 随机重新组织牌。

deck.sort(key=by_poker_order)
deck.sort(key=by_suit)

我们先按扑克顺序然后按花色对牌组进行排序。

for k, g in groupby(deck, key=lambda c: c[-1]):
    print(k, list(g))

我们使用 groupby 函数按花色形成四个组。

$ ./main.py
♠ ['2♠', '3♠', '4♠', '5♠', '6♠', '7♠', '8♠', '9♠', '10♠', 'J♠', 'Q♠', 'K♠', 'A♠']
♣ ['2♣', '3♣', '4♣', '5♣', '6♣', '7♣', '8♣', '9♣', '10♣', 'J♣', 'Q♣', 'K♣', 'A♣']
♥ ['2♥', '3♥', '4♥', '5♥', '6♥', '7♥', '8♥', '9♥', '10♥', 'J♥', 'Q♥', 'K♥', 'A♥']
♦ ['2♦', '3♦', '4♦', '5♦', '6♦', '7♦', '8♦', '9♦', '10♦', 'J♦', 'Q♦', 'K♦', 'A♦']

Python 排序自定义复杂对象列表 - 硬币包

我们有一个自定义对象,一个命名元组,它有一种特定的排序方式。

注意:根据 Python 文档,sortsorted 在排序时仅使用 __lt__ 魔术方法。所以我们只需要实现这个方法。但是,PEP8 建议出于安全和代码完整性的考虑,实现所有六个操作(__eq____ne____lt____le____gt____ge__)。

来自 functools 模块的 total_ordering 装饰器有助于减少样板代码。total_ordering 需要实现 __eq__ 和剩余方法之一。

sort_coins.py
#!/usr/bin/python

from typing import NamedTuple
from functools import total_ordering

# a gold coin equals to two silver and six bronze coins


class Coin(NamedTuple):

    rank: str


@total_ordering
class Pouch:

    def __init__(self):
        self.bag = []

    def add(self, coin):

        self.bag.append(coin)

    def __eq__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 == val2:
            return True
        else:
            return False

    def __lt__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 < val2:
            return True
        else:
            return False

    def __str__(self):

        return f'Pouch with: {self.bag}'

    def __evaluate(self, other):

        val1 = 0
        val2 = 0

        for coin in self.bag:

            if coin.rank == 'g':
                val1 += 6

            if coin.rank == 's':
                val1 += 3

            if coin.rank == 'b':
                val1 += 1

        for coin in other.bag:

            if coin.rank == 'g':
                val2 += 6

            if coin.rank == 's':
                val2 += 3

            if coin.rank == 'b':
                val2 += 1

        return val1, val2


def create_pouches():

    p1 = Pouch()

    p1.add(Coin('g'))
    p1.add(Coin('b'))
    p1.add(Coin('s'))

    p2 = Pouch()

    p2.add(Coin('g'))
    p2.add(Coin('s'))

    p3 = Pouch()

    p3.add(Coin('b'))
    p3.add(Coin('s'))
    p3.add(Coin('s'))

    p4 = Pouch()

    p4.add(Coin('b'))
    p4.add(Coin('s'))

    p5 = Pouch()

    p5.add(Coin('g'))
    p5.add(Coin('s'))
    p5.add(Coin('s'))
    p5.add(Coin('b'))
    p5.add(Coin('b'))
    p5.add(Coin('b'))

    p6 = Pouch()

    p6.add(Coin('b'))
    p6.add(Coin('b'))
    p6.add(Coin('b'))
    p6.add(Coin('b'))
    p6.add(Coin('b'))

    p7 = Pouch()
    p7.add(Coin('g'))

    p8 = Pouch()
    p8.add(Coin('g'))
    p8.add(Coin('g'))
    p8.add(Coin('s'))

    bag = [p1, p2, p3, p4, p5, p6, p7, p8]

    return bag


bag = create_pouches()
bag.sort()

for e in bag:
    print(e)

在此示例中,我们对硬币袋进行排序。有三种类型的硬币:金币、银币和铜币。一个金币等于两个银币和六个铜币。(因此,一个银币等于三个铜币。)

class Coin(NamedTuple):

    rank: str

我们的自定义对象是命名元组,它有一个属性:rank

@total_ordering
class Pouch:

    def __init__(self):
        self.bag = []

    def add(self, coin):

        self.bag.append(coin)
...

Pouch 有一个内部的 self.bag 列表用于存储其硬币。在类中,我们有两个比较方法:__lt____eq__@total_ordering 装饰器提供了其余方法。

def __lt__(self, other):

    val1, val2 = self.__evaluate(other)

    if val1 < val2:
        return True
    else:
        return False

__lt__ 方法用于 Python 排序函数比较两个对象。我们必须计算两个袋子中所有硬币的价值并进行比较。

def __str__(self):

    return f'Pouch with: {self.bag}'

__str__ 提供 Pouch 对象的易读表示。

def __evaluate(self, other):

    val1 = 0
    val2 = 0

    for coin in self.bag:

        if coin.rank == 'g':
            val1 += 6

        if coin.rank == 's':
            val1 += 3

        if coin.rank == 'b':
            val1 += 1

    for coin in other.bag:

        if coin.rank == 'g':
            val2 += 6

        if coin.rank == 's':
            val2 += 3

        if coin.rank == 'b':
            val2 += 1

    return val1, val2

__evaluate 方法计算两个袋子的价值。它将两个值都返回给 __lt__ 进行比较。

def create_pouches():

    p1 = Pouch()

    p1.add(Coin('g'))
    p1.add(Coin('b'))
    p1.add(Coin('s'))

    p2 = Pouch()

    p2.add(Coin('g'))
    p2.add(Coin('s'))
...

create_pouches 函数中,我们创建了八个具有不同数量硬币的袋子。

bag.sort()

for e in bag:
    print(e)

我们对硬币袋进行排序,然后打印排序后袋子中的元素。

$ ./coins.py
Pouch with: [Coin(rank='b'), Coin(rank='s')]
Pouch with: [Coin(rank='b'), Coin(rank='b'), Coin(rank='b'), Coin(rank='b'), Coin(rank='b')]
Pouch with: [Coin(rank='g')]
Pouch with: [Coin(rank='b'), Coin(rank='s'), Coin(rank='s')]
Pouch with: [Coin(rank='g'), Coin(rank='s')]
Pouch with: [Coin(rank='g'), Coin(rank='b'), Coin(rank='s')]
Pouch with: [Coin(rank='g'), Coin(rank='s'), Coin(rank='s'), Coin(rank='b'), Coin(rank='b'), Coin(rank='b')]
Pouch with: [Coin(rank='g'), Coin(rank='g'), Coin(rank='s')]

这是输出。有两个金币和一个银币的袋子最有价值。

来源

Python 数据结构 - 语言参考

在本文中,我们介绍了 Python 中列表的排序操作。

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面有十多年的经验。

列出所有 Python 教程