按两个字段对Python列表进行排序

247
我从已排序的 CSV 文件中创建了以下列表。
list1 = sorted(csv1, key=operator.itemgetter(1))

我想按照两个条件对列表进行排序:首先是根据第一个字段的值,然后再根据第二个字段的值。我该如何做到这一点?


我们是否让这个问题保持原样,仅限于“长度为两个内置类型(例如字符串/整数/浮点数)的列表列表”?还是我们也允许“用户定义对象列表”,正如标题所示,这也是允许的,这种情况下答案是“在您的类上定义__lt __()方法或继承一些已经定义了该方法的类”?那将使它成为一个更好的规范。 - smci
10个回答

454

在使用lambda函数时,无需导入任何内容。
以下代码按第一个元素排序,然后按第二个元素排序。您还可以按一个字段升序和另一个字段降序排序,例如:

sorted_list = sorted(list, key=lambda x: (x[0], -x[1]))

16
好的。如您在上面的主要答案评论中提到的,这是使用不同排序顺序进行多个排序的最佳(唯一?)方式。可以强调一下。另外,您的文本没有表明您对第二个元素进行了降序排序。 - PeterVermont
2
@user1700890 我原本以为该字段已经是字符串类型的。默认情况下,它应该按字母顺序对字符串进行排序。如果你的问题与这里的答案或原始问题无关,请单独在SO上发布你自己的问题。 - pbible
10
--x[1]中代表取相反数的意思。 - jan
10
@jan 这是倒序排列。 - jaap
4
在某个特定情况下无法使用。接受的解决方案也无法使用。例如,要用作键的列都是字符串,无法转换为数字。其次,需要按一个列升序排列,并按另一个列降序排列。 - coder.in.me
显示剩余8条评论

182

像这样:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))

2
+1:比我的更优雅。我忘了itemgetter可以使用多个索引。 - dappawit
9
operator是一个需要导入的模块。 - trapicki
6
如果我想在一个元素上按升序排序,另一个元素上按降序排序,使用itemgetter应该如何操作? - ashish
6
@ashish,请看下面我的答案,使用lambda函数更加清晰,如果你愿意,可以按“-x[1]”或甚至“x[0]+x[1]”排序。 - jaap
如果一个条件是反向模式呢? - YaserKH

28

Python有一个稳定的排序算法,所以只要性能不是问题,最简单的方法就是先按第二个字段进行排序,然后再按第一个字段进行排序。

这将给您想要的结果,唯一的问题是,如果列表很大(或者您想经常进行排序),两次调用排序可能会导致过高的开销。

list1 = sorted(csv1, key=operator.itemgetter(2))
list1 = sorted(list1, key=operator.itemgetter(1))
这种方法也使得处理需要对某些列进行反向排序的情况变得容易,只需在必要时包含'reverse=True'参数即可。
否则,您可以向itemgetter传递多个参数或手动构建一个元组。这可能会更快,但它的问题是如果要反向排序一些列,则不能很好地推广(数值列仍可以通过取负数进行反转,但这会导致排序不稳定)。
因此,如果您不需要任何列进行反向排序,请选择向itemgetter传递多个参数;如果可能需要进行反向排序,并且列不是数值或者您希望保持排序稳定性,请选择多个连续排序。
编辑:针对那些不理解这如何回答原始问题的评论者,这里有一个例子,显示了排序的稳定性如何确保我们可以根据多个条件对数据进行分别排序并最终得到排序后的数据。
DATA = [
    ('Jones', 'Jane', 58),
    ('Smith', 'Anne', 30),
    ('Jones', 'Fred', 30),
    ('Smith', 'John', 60),
    ('Smith', 'Fred', 30),
    ('Jones', 'Anne', 30),
    ('Smith', 'Jane', 58),
    ('Smith', 'Twin2', 3),
    ('Jones', 'John', 60),
    ('Smith', 'Twin1', 3),
    ('Jones', 'Twin1', 3),
    ('Jones', 'Twin2', 3)
]

# Sort by Surname, Age DESCENDING, Firstname
print("Initial data in random order")
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred''')
DATA.sort(key=lambda row: row[1])

for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.''')
DATA.sort(key=lambda row: row[2], reverse=True)
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.
''')
DATA.sort(key=lambda row: row[0])
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

以下是可运行的示例,但为了避免让人们运行它,输出结果已省略:

Initial data in random order
Jones      Jane       58
Smith      Anne       30
Jones      Fred       30
Smith      John       60
Smith      Fred       30
Jones      Anne       30
Smith      Jane       58
Smith      Twin2      3
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Jones      Twin2      3

First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Jones      Jane       58
Smith      Jane       58
Smith      John       60
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.
Smith      John       60
Jones      John       60
Jones      Jane       58
Smith      Jane       58
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.

Jones      John       60
Jones      Jane       58
Jones      Anne       30
Jones      Fred       30
Jones      Twin1      3
Jones      Twin2      3
Smith      John       60
Smith      Jane       58
Smith      Anne       30
Smith      Fred       30
Smith      Twin1      3
Smith      Twin2      3

请特别注意第二步中reverse=True参数的使用,它保持了firstname的顺序,而如果只是先排序再反转列表,则会失去对第三个排序关键字所需的顺序。


2
稳定排序并不意味着它不会忘记您先前的排序方式。这个答案是错误的。 - Mike Axiak
8
稳定排序意味着你可以通过先按列c排序,然后按列b排序,最后按列a排序来简单地排序。除非你想进一步解释你的评论,否则我认为是你错了。 - Duncan
8
这个答案是正确的,但对于较大的列表来说并不理想:如果列表已经部分排序了,那么通过大量洗牌列表会失去Python排序的优化。@Mike,你是错误的;我建议在宣布答案错误之前实际测试一下。 - Glenn Maynard
7
@MikeAxiak 在第9条评论中提到:http://docs.python.org/2/library/stdtypes.html#index-29指出,从Python 2.3开始,sort()方法保证是稳定的。如果排序是稳定的,就保证不改变相等元素之间的相对顺序——这对于多次排序非常有帮助(例如,先按部门排序,然后按薪资等级排序)。 - trapicki
1
这不正确,因为它没有回答他所问的问题。他想要一个按第一个索引排序的列表,并且在第一个索引存在并列的情况下,他希望使用第二个索引作为排序标准。稳定排序仅保证在所有事情都相等的情况下,原始顺序将是项目出现的顺序。 - Jon
显示剩余4条评论

20
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )

4
我认为turple()函数不能接收两个参数(或者更确切地说,三个参数,如果你算上self的话)。 - Filipe Correia
3
元组只能接受一个参数。 - therealprashant
1
return 语句应该是 return tuple((x[1], x[2])) 或者简单地写成 return x[1], x[2]。如果你想要按不同的方向进行排序,请参考下面的 @jaap 的答案。 - Jo Kachikaran
如果你想使用元组构造器而不是仅仅使用元组显示列表x[1],x[2],那么可以使用tuple(x[1:3])。或者使用keyfunc = operator.itemgetter(1, 2),甚至不需要自己编写一个函数。 - abarnert
我可以这样做吗,list1 = sorted(csv1, key=lambda x: x[1] and x[2])?如果不行,那么在这种情况下会出现什么行为? - ahmetbulut

3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

我们也可以使用带有lambda的.sort两次,因为Python的排序是原地和稳定的。这将首先根据第二个元素x [1]对列表进行排序。然后,它将按照第一个元素x [0]进行排序(最高优先级)。
employees[0] = "Employee's Name"
employees[1] = "Employee's Salary"

这相当于做以下操作:
employees.sort(key = lambda x:(x[0], x[1]))

2
不,这个排序规则需要优先考虑,然后才是第二个。 - CodeFarmer

2

使用下面的方法对字典列表进行排序,会按照工资作为第一列和年龄作为第二列进行降序排序。

d=[{'salary':123,'age':23},{'salary':123,'age':25}]
d=sorted(d, key=lambda i: (i['salary'], i['age']),reverse=True)

输出:[{'salary': 123, 'age': 25}, {'salary': 123, 'age': 23}]


1
如果您想根据数组进行升序和降序排序,请按照以下方法使用 lambda 函数。考虑以下示例,输入:[[1,2],[3,3],[2,1],[1,1],[4,1],[3,1]],期望输出为:[[4, 1], [3, 1], [3, 3], [2, 1], [1, 1], [1, 2]],代码如下:
    arr = [[1,2],[3,3],[2,1],[1,1],[4,1],[3,1]]
    arr.sort(key=lambda ele: (ele[0], -ele[1]), reverse=True)
    # output [[4, 1], [3, 1], [3, 3], [2, 1], [1, 1], [1, 2]]

负号是导致结果不符合预期的原因。

1

在阅读了本主题中的答案后,我编写了一个通用解决方案,适用于任意数量的列:

def sort_array(array, *columns):
    for col in columns:
        array.sort(key = lambda x:x[col])

OP会这样调用它:

sort_array(list1, 2, 1)

按照第二列排序,然后按照第一列排序。(最重要的列放在最后)

1

您可以使用以下升序:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]))

或者按降序排列可以使用:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]),reverse=True)

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接