按照第二个元素(整数值)对元组列表进行排序

544

我有一个元组列表,看起来像这样:

[('abc', 121),('abc', 231),('abc', 148), ('abc',221)]

我想按元组内整数值的升序对此列表进行排序。这种排序方式可行吗?


我刚刚发现,如果没有给定参数,Python的sorted()方法会首先按照元组中的第一个值进行排序,然后是第二个值。例如, data = [('zbc', 121),('abc', 231),('gbc', 148), ('abc',221)] print(sorted(data)) print(sorted(data)) 会产生以下结果,注意第二个值也被排序了: data = [('zbc', 121),('abc', 231),('gbc', 148), ('abc',221)] print(sorted(data)) [('abc', 221), ('abc', 231), ('gbc', 148), ('zbc', 121)] - undefined
9个回答

839

尝试使用sorted()key关键字参数,默认情况下按升序排序:

sorted(
    [('abc', 121), ('abc', 231), ('abc', 148), ('abc', 221)], 
    key=lambda x: x[1]
)

key 应该是一个函数,用于确定如何从数据结构中检索可比较的元素。在您的情况下,它是元组的第二个元素,因此我们访问 [1]

为了优化,请参见 jamylak 的响应,使用 operator.itemgetter(1),这实际上是 lambda x: x[1] 的更快版本。


26
虽然很明显,但是 sorted() 函数不会原地排序,因此以下代码会将排序结果赋值给 sorted_list:sorted_list = sorted([('abc', 121),('abc', 231),('abc', 148), ('abc',221)], key=lambda x: x[1]) - Vesanto
28
对于从大到小排序,请使用 reverse=True。 - jonincanada
4
这在 Python 3.7 上仍然能够良好运作。 - jftuga
5
如果你想要添加多个键作为元组,你可以使用负号使其中一个键反转排序顺序。这将按照第一个元素排序,然后再按照第二个元素排序:sorted(some_list, lambda x: (x[0], -x[1],)) - Seraf
如果我们不提供任何键,上述情况会发生什么? - Hemanth Bakaya
3
我只是想说,这是我在Stack Overflow上访问最频繁的页面,我已经来过大约500次了。谢谢你,Cheeken,但愿我能记住这一行代码。 - negfrequency

236
>>> from operator import itemgetter
>>> data = [('abc', 121),('abc', 231),('abc', 148), ('abc',221)]
>>> sorted(data,key=itemgetter(1))
[('abc', 121), ('abc', 148), ('abc', 221), ('abc', 231)]

在我看来,使用itemgetter在这种情况下比@cheeken的解决方案更易读。它也更快,因为几乎所有的计算都将在c端完成,而不是通过使用lambda

>python -m timeit -s "from operator import itemgetter; data = [('abc', 121),('abc', 231),('abc', 148), ('abc',221)]" "sorted(data,key=itemgetter(1))"
1000000 loops, best of 3: 1.22 usec per loop

>python -m timeit -s "data = [('abc', 121),('abc', 231),('abc', 148), ('abc',221)]" "sorted(data,key=lambda x: x[1])"
1000000 loops, best of 3: 1.4 usec per loop

15
我同意itemgetter()是更好的解决方案。但是,我认为一个lambda表达式会更清楚地说明key函数的作用。 - cheeken
1
然而,当我运行了你的速度测试时,我注意到“肉眼”看起来应该更快的那个,实际上明显更慢。我想了一会儿,然后将Python超时模块排除在外,只使用Linux时间。即 time \python -c "the code"`` 然后我得到了你所说的“肉眼”结果,以及更快的系统时钟时间。我仍然不确定这是为什么,但它是可以重现的。我猜这与加载模块的开销有关,但对我来说还不太清楚。 - Jeff Sheffield
2
@JeffSheffield:注意,jamylak是在设置代码(时间之外)中进行导入,而不是测试代码。这是非常合理的,因为大多数程序需要进行多次排序,或需要对更大的集合进行排序,但它们只会进行一次导入。 (对于那些只需要进行一次较小排序的程序...好吧,你说的是不到一微秒的差异,所以谁在乎呢?) - abarnert
@abarnert FYI:jamylak正在python -m timeit -s内部执行导入,但是我认为你说的在生产场景中只需支付一次库加载惩罚是正确的。至于谁关心那微秒...你会关心,因为假设你的排序数据将变得非常大,那么一旦数据集增长,那微秒就会变成真正的秒数。 - Jeff Sheffield
@JeffSheffield:这正是关键所在:导入的成本不会随着数据增长而增加,因此即使对于一个相对较小的排序,你支付的1us中似乎有很大一部分是导入成本,但对于一个大型排序或一堆小型排序,它将成为你支付的500ms中无关紧要的一部分。 - abarnert
显示剩余4条评论

51

在Cheeken的回答基础上,这是如何按第二项降序排列元组列表的方法。

sorted([('abc', 121),('abc', 231),('abc', 148), ('abc',221)],key=lambda x: x[1], reverse=True)

1
请注意,原始列表不会被更改。sorted函数只是为您生成一个已排序的新列表。 - ZhaoGang

45
作为一名 Python 初学者,我只想提一下,如果数据确实看起来像这样:

As a python neophyte, I just wanted to mention that if the data did actually look like this:


data = [('abc', 121),('abc', 231),('abc', 148), ('abc',221)]

然后 sorted() 会自动按元组中的第二个元素排序,因为第一个元素都相同。


29

使用原地排序,可以使用

foo = [(list of tuples)]
foo.sort(key=lambda x:x[0]) #To sort by first element of the tuple

2
尽管这个答案可能是正确的,但最好解释一下为什么这个答案是正确的,而不是只提供代码。此外,这几乎是一个已经存在并在5年前被接受的答案的确切答案,因此这并没有为网站增加任何内容。看看更新的问题,帮助他人吧! - JNYRanger
14
实际上,这有助于寻找原地排序的人。 - leoschet
虽然这很有帮助,但更适合作为评论添加到建议的答案中,指示如何使用与该答案提供的相同方法来就地完成相同任务。 - Michael DiStefano

15

来自Python维基百科:

>>> from operator import itemgetter, attrgetter    
>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]    
>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

x = [[[5,3],1.0345],[[5,6],5.098],[[5,4],4.89],[[5,1],5.97]] 我们可以使用itemgetter()根据x [0] [1]中的元素进行排序吗? - nidHi

8
为了避免使用Lambda表达式的方法,首先需要定义您自己的函数:
def MyFn(a):
    return a[1]

那么:

sorted([('abc', 121),('abc', 231),('abc', 148), ('abc',221)], key=MyFn)

2
这有什么好处? - dromtrund
6
一个好处是你可以定义一个函数并在代码中任何地方使用它,而不必在多个代码区域中都写上 lambda x: x[1] - Tom Myddeltyn
2
另一个好处是,如果它是一个独立的函数,你可以更好地记录/注释。 - uli42

5
对于Python 2.7及以上版本,这个方法可行,这使得被接受的答案稍微更易读了一些:
sorted([('abc', 121),('abc', 231),('abc', 148), ('abc',221)], key=lambda (k, val): val)

0

OP中排序值为整数与问题本身无关。换句话说,如果排序值是文本,则接受的答案也适用。我提到这一点还是为了指出在排序过程中可以修改排序方式(例如,考虑大小写)。

>>> sorted([(121, 'abc'), (231, 'def'), (148, 'ABC'), (221, 'DEF')], key=lambda x: x[1])
[(148, 'ABC'), (221, 'DEF'), (121, 'abc'), (231, 'def')]
>>> sorted([(121, 'abc'), (231, 'def'), (148, 'ABC'), (221, 'DEF')], key=lambda x: str.lower(x[1]))
[(121, 'abc'), (148, 'ABC'), (231, 'def'), (221, 'DEF')]

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