Làm thế nào để sắp xếp danh sách các đối tượng dựa trên một thuộc tính của các đối tượng?


804

Tôi đã có một danh sách các đối tượng Python mà tôi muốn sắp xếp theo một thuộc tính của chính các đối tượng đó. Danh sách này trông như:

>>> ut
[<Tag: 128>, <Tag: 2008>, <Tag: <>, <Tag: actionscript>, <Tag: addresses>,
 <Tag: aes>, <Tag: ajax> ...]

Mỗi đối tượng có một số lượng:

>>> ut[1].count
1L

Tôi cần sắp xếp danh sách theo số lượng giảm dần.

Tôi đã thấy một số phương pháp cho việc này, nhưng tôi đang tìm cách thực hành tốt nhất trong Python.



1
Sắp xếp CÁCH cho những ai đang tìm kiếm thêm thông tin về cách sắp xếp trong Python.
Jeyekomon

1
ngoài toán tử.attrgetter ('property_name'), bạn cũng có thể sử dụng functor làm khóa như object_list.sort (key = my_sorting_functor ('my_key')), cố tình bỏ việc triển khai.
shjer vijay

Câu trả lời:


1313
# To sort the list in place...
ut.sort(key=lambda x: x.count, reverse=True)

# To return a new list, use the sorted() built-in function...
newlist = sorted(ut, key=lambda x: x.count, reverse=True)

Thêm về cách sắp xếp theo các phím .


1
Không vấn đề gì. btw, nếu muhuk đúng và đó là danh sách các đối tượng Django, bạn nên xem xét giải pháp của anh ấy. Tuy nhiên, đối với trường hợp chung của việc sắp xếp các đối tượng, giải pháp của tôi có lẽ là cách thực hành tốt nhất.
Triptych

43
Trên các danh sách lớn, bạn sẽ có hiệu suất tốt hơn bằng cách sử dụng toán tử.attrgetter ('tính') làm khóa của mình. Đây chỉ là một hình thức tối ưu hóa (mức thấp hơn) của hàm lambda trong câu trả lời này.
David Eyk

4
Cảm ơn câu trả lời tuyệt vời. Trong trường hợp nếu đó là danh sách từ điển và 'Count' là một trong những từ khóa của nó thì nó cần được thay đổi như dưới đây: ut.sort (key = lambda x: x ['Count'], Reverse = True)
dganesh2002

Tôi cho rằng nó xứng đáng được cập nhật sau: nếu có nhu cầu sắp xếp theo nhiều trường, có thể đạt được bằng các lệnh gọi liên tiếp để sắp xếp (), vì python đang sử dụng thuật toán sắp xếp ổn định.
zzz777

86

Một cách có thể nhanh nhất, đặc biệt nếu danh sách của bạn có nhiều hồ sơ, là sử dụng operator.attrgetter("count"). Tuy nhiên, điều này có thể chạy trên phiên bản tiền vận hành của Python, vì vậy thật tuyệt khi có cơ chế dự phòng. Bạn có thể muốn làm như sau, sau đó:

try: import operator
except ImportError: keyfun= lambda x: x.count # use a lambda if no operator module
else: keyfun= operator.attrgetter("count") # use operator since it's faster than lambda

ut.sort(key=keyfun, reverse=True) # sort in-place

7
Ở đây tôi sẽ sử dụng tên biến "keyfun" thay vì "cmpfun" để tránh nhầm lẫn. Phương thức sort () cũng chấp nhận hàm so sánh thông qua đối số cmp =.
akaihola

Điều này dường như không hoạt động nếu đối tượng có các thuộc tính được thêm động, (nếu bạn đã thực hiện self.__dict__ = {'some':'dict'}sau __init__phương thức). Tôi không biết tại sao nó lại khác đi.
tutuca

@tutuca: Tôi chưa bao giờ thay thế __dict__. Lưu ý rằng "một đối tượng có các thuộc tính được thêm động" và "thiết lập __dict__thuộc tính của đối tượng " là các khái niệm gần như trực giao. Tôi đang nói rằng bởi vì nhận xét của bạn dường như ngụ ý rằng việc đặt __dict__thuộc tính là một yêu cầu để thêm động các thuộc tính.
tzot

@tzot: Tôi đang nhìn đúng vào điều này: github.com/stochastic-technology/goatfish/blob/master/ , và sử dụng trình lặp đó ở đây: github.com/TallerTechnology/dishey/blob/master/app.py#L28 tăng lỗi thuộc tính. Có lẽ vì python3, nhưng vẫn ...
tutuca

1
@tzot: nếu tôi hiểu việc sử dụng operator.attrgetter, tôi có thể cung cấp một hàm với bất kỳ tên thuộc tính nào và trả về một bộ sưu tập đã sắp xếp.
Tôi chấp nhận

64

Bạn đọc nên chú ý rằng key = phương thức:

ut.sort(key=lambda x: x.count, reverse=True)

nhanh hơn nhiều lần so với việc thêm các toán tử so sánh phong phú vào các đối tượng. Tôi đã rất ngạc nhiên khi đọc nó (trang 485 của "Python in a Nutshell"). Bạn có thể xác nhận điều này bằng cách chạy thử nghiệm trên chương trình nhỏ này:

#!/usr/bin/env python
import random

class C:
    def __init__(self,count):
        self.count = count

    def __cmp__(self,other):
        return cmp(self.count,other.count)

longList = [C(random.random()) for i in xrange(1000000)] #about 6.1 secs
longList2 = longList[:]

longList.sort() #about 52 - 6.1 = 46 secs
longList2.sort(key = lambda c: c.count) #about 9 - 6.1 = 3 secs

Các thử nghiệm của tôi, rất tối thiểu, cho thấy loại đầu tiên chậm hơn 10 lần, nhưng cuốn sách nói rằng nó chỉ chậm hơn khoảng 5 lần nói chung. Lý do họ nói là do thuật toán sắp xếp tối ưu hóa cao được sử dụng trong python ( timsort ).

Tuy nhiên, rất kỳ lạ là .sort (lambda) nhanh hơn so với .sort () cũ. Tôi hy vọng họ khắc phục điều đó.


1
Xác định __cmp__là tương đương với gọi .sort(cmp=lambda), không .sort(key=lambda), vì vậy nó không phải là lạ.
tzot

@tzot hoàn toàn chính xác. Loại đầu tiên phải so sánh các đối tượng với nhau nhiều lần. Loại thứ hai chỉ truy cập mỗi đối tượng một lần để trích xuất giá trị đếm của nó và sau đó nó thực hiện một loại số đơn giản được tối ưu hóa cao. Một so sánh công bằng hơn sẽ là longList2.sort(cmp = cmp). Tôi đã thử điều này và nó thực hiện gần giống như .sort(). (Ngoài ra: lưu ý rằng tham số sắp xếp "cmp" đã bị xóa trong Python 3.)
Bryan Roach

43

Phương pháp hướng đối tượng

Đó là cách thực hành tốt để tạo logic sắp xếp đối tượng, nếu có thể, một thuộc tính của lớp thay vì được kết hợp trong mỗi trường hợp, việc đặt hàng là bắt buộc.

Điều này đảm bảo tính nhất quán và loại bỏ sự cần thiết của mã soạn sẵn.

Tối thiểu, bạn nên chỉ định __eq____lt__hoạt động để làm việc này. Sau đó chỉ cần sử dụng sorted(list_of_objects).

class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return self.rank == other.rank and self.suit == other.suit

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

hand = [Card(10, 'H'), Card(2, 'h'), Card(12, 'h'), Card(13, 'h'), Card(14, 'h')]
hand_order = [c.rank for c in hand]  # [10, 2, 12, 13, 14]

hand_sorted = sorted(hand)
hand_sorted_order = [c.rank for c in hand_sorted]  # [2, 10, 12, 13, 14]

1
Đó là những gì tôi đang tìm kiếm! Bạn có thể chỉ cho chúng tôi một số tài liệu giải thích tại sao __eq____lt__các yêu cầu thực hiện tối thiểu không?
FriendFX

1
@FriendFX, tôi tin rằng nó được ngụ ý bởi điều này :•The sort routines are guaranteed to use __lt__() when making comparisons between two objects...
jpp

2
@FriendFX: Xem portingguide.readthedocs.io/en/latest/comparisons.html để so sánh và sắp xếp
Cornel Masson

37
from operator import attrgetter
ut.sort(key = attrgetter('count'), reverse = True)

16

Nó trông giống như một danh sách các trường hợp mô hình Django ORM.

Tại sao không sắp xếp chúng theo truy vấn như thế này:

ut = Tag.objects.order_by('-count')

Đó là, nhưng bằng cách sử dụng tính năng gắn thẻ django, vì vậy tôi đã sử dụng tính năng tích hợp để lấy Thẻ được đặt bằng cách sử dụng cho một bộ truy vấn cụ thể, như vậy: Tag.objects.usage_for_queryset (Queryset, Counts = True)
Nick Sergeant

11

Thêm các toán tử so sánh phong phú vào lớp đối tượng, sau đó sử dụng phương thức sort () của danh sách.
Xem so sánh phong phú trong trăn .


Cập nhật : Mặc dù phương pháp này sẽ hoạt động, tôi nghĩ giải pháp từ Triptych phù hợp hơn với trường hợp của bạn vì cách đơn giản hơn.


3

Nếu thuộc tính bạn muốn sắp xếp theo là một thuộc tính , thì bạn có thể tránh nhập operator.attrgettervà sử dụng thuộc tínhfget phương thức thay thế.

Ví dụ, đối với một lớp Circlecó thuộc tính, radiuschúng ta có thể sắp xếp danh sách circlestheo bán kính như sau:

result = sorted(circles, key=Circle.radius.fget)

Đây không phải là tính năng nổi tiếng nhất nhưng thường giúp tôi tiết kiệm một dòng với việc nhập.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.