Sắp xếp một danh sách theo nhiều thuộc tính?


456

Tôi có một danh sách các danh sách:

[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]

Nếu tôi muốn sắp xếp theo một yếu tố, giả sử yếu tố cao / ngắn, tôi có thể thực hiện thông qua s = sorted(s, key = itemgetter(1)).

Nếu tôi muốn sắp xếp theo cả chiều cao / ngắn và màu sắc, tôi có thể thực hiện sắp xếp hai lần, một lần cho mỗi yếu tố, nhưng có cách nào nhanh hơn không?



8
Nếu bạn sử dụng bộ dữ liệu thay vì danh sách, các lệnh python sắp xếp theo các mục từ trái sang phải khi bạn chạy sort. Đó là, sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)].
Mateen Ulhaq

Câu trả lời:


771

Khóa có thể là một hàm trả về một tuple:

s = sorted(s, key = lambda x: (x[1], x[2]))

Hoặc bạn có thể đạt được điều tương tự bằng cách sử dụng itemgetter(nhanh hơn và tránh lệnh gọi hàm Python):

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

Và lưu ý rằng ở đây bạn có thể sử dụng sortthay vì sử dụng sortedvà sau đó gán lại:

s.sort(key = operator.itemgetter(1, 2))

20
Để hoàn thiện từ thời gian: đối với tôi trước tiên đã cho 6 chúng tôi mỗi vòng lặp và 4,4 lần thứ hai cho mỗi vòng lặp
Brian Larsen

10
Có cách nào để sắp xếp cái thứ nhất tăng dần và cái thứ hai giảm dần không? (Giả sử cả hai thuộc tính là các chuỗi, vì vậy không có hack nào như thêm -số nguyên)
Martin Thoma

73
làm thế nào về nếu tôi muốn áp dụng revrse=Trueduy nhất x[1]là có thể?
Amyth

28
@moose, @Amyth, để đảo ngược chỉ một thuộc tính, bạn có thể sắp xếp hai lần: đầu tiên theo thứ cấp s = sorted(s, key = operator.itemgetter(2))sau đó theo chính s = sorted(s, key = operator.itemgetter(1), reverse=True)Không lý tưởng, nhưng hoạt động.
tomcounsell

52
@Amyth hoặc một tùy chọn khác, nếu khóa là số, để làm cho nó đảo ngược, bạn có thể nhân nó bằng cách -1.
Serge

37

Tôi không chắc đây có phải là phương pháp pythonic nhất không ... Tôi đã có một danh sách các bộ dữ liệu cần sắp xếp thứ 1 bằng cách giảm dần các giá trị nguyên và thứ 2 theo thứ tự bảng chữ cái. Điều này yêu cầu đảo ngược sắp xếp số nguyên nhưng không phải là sắp xếp theo thứ tự abc. Đây là giải pháp của tôi: (khi đang bay trong kỳ thi btw, tôi thậm chí còn không biết bạn có thể 'sắp xếp' các chức năng được sắp xếp)

a = [('Al', 2),('Bill', 1),('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]  
b = sorted(sorted(a, key = lambda x : x[0]), key = lambda x : x[1], reverse = True)  
print(b)  
[('Abel', 3), ('Al', 2), ('Carol', 2), ('Zeke', 2), ('Bill', 1), ('Chris', 1)]

13
vì thứ 2 là một con số, nó hoạt động để làm điều đó như b = sorted(a, key = lambda x: (-x[1], x[0]))thể thấy rõ hơn về tiêu chí nào được áp dụng đầu tiên. đối với hiệu quả tôi không chắc chắn, ai đó cần thời gian.
Andrei-Niculae Petre

5

Có vẻ như bạn có thể sử dụng listthay vì a tuple. Điều này trở nên quan trọng hơn tôi nghĩ khi bạn đang lấy các thuộc tính thay vì 'chỉ số ma thuật' của một danh sách / bộ dữ liệu.

Trong trường hợp của tôi, tôi muốn sắp xếp theo nhiều thuộc tính của một lớp, trong đó các khóa đến là các chuỗi. Tôi cần sắp xếp khác nhau ở những nơi khác nhau và tôi muốn một loại sắp xếp mặc định chung cho lớp cha mà khách hàng đang tương tác; chỉ phải ghi đè 'khóa sắp xếp' khi tôi thực sự 'cần', nhưng cũng theo cách mà tôi có thể lưu trữ chúng dưới dạng danh sách mà lớp có thể chia sẻ

Vì vậy, trước tiên tôi đã định nghĩa một phương thức trợ giúp

def attr_sort(self, attrs=['someAttributeString']:
  '''helper to sort by the attributes named by strings of attrs in order'''
  return lambda k: [ getattr(k, attr) for attr in attrs ]

sau đó sử dụng nó

# would defined elsewhere but showing here for consiseness
self.SortListA = ['attrA', 'attrB']
self.SortListB = ['attrC', 'attrA']
records = .... #list of my objects to sort
records.sort(key=self.attr_sort(attrs=self.SortListA))
# perhaps later nearby or in another function
more_records = .... #another list
more_records.sort(key=self.attr_sort(attrs=self.SortListB))

Điều này sẽ sử dụng hàm lambda được tạo sắp xếp danh sách theo object.attrAvà sau đó object.attrBgiả sử objectcó một getter tương ứng với các tên chuỗi được cung cấp. Và trường hợp thứ hai sẽ sắp xếp object.attrCsau đó object.attrA.

Điều này cũng cho phép bạn có khả năng đưa ra các lựa chọn sắp xếp bên ngoài để được chia sẻ bởi người tiêu dùng, bài kiểm tra đơn vị hoặc để họ có thể cho bạn biết họ muốn sắp xếp như thế nào cho một số thao tác trong api của bạn chỉ bằng cách đưa ra danh sách cho bạn ghép chúng để thực hiện kết thúc trở lại của bạn.


Công việc tốt đẹp. Điều gì nếu các thuộc tính nên được sắp xếp theo thứ tự khác nhau? Giả sử attrA nên được sắp xếp tăng dần và attrB giảm dần? Có một giải pháp nhanh chóng trên đầu trang này? Cảm ơn!
mhn_namak

4

Vài năm đến bữa tiệc muộn nhưng tôi muốn cả hai sắp xếp theo 2 tiêu chí sử dụng reverse=True. Trong trường hợp người khác muốn biết làm thế nào, bạn có thể gói các tiêu chí (hàm) của mình trong ngoặc đơn:

s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)

1

Đây là một cách: Về cơ bản, bạn viết lại hàm sắp xếp của mình để lấy danh sách các hàm sắp xếp, mỗi hàm sắp xếp so sánh các thuộc tính bạn muốn kiểm tra, trên mỗi bài kiểm tra sắp xếp, bạn xem và xem hàm cmp có trả về giá trị khác không nếu vậy phá vỡ và gửi giá trị trả lại. Bạn gọi nó bằng cách gọi Lambda của một chức năng của một danh sách Lambdas.

Ưu điểm của nó là nó không truyền dữ liệu đơn lẻ mà không phải là một loại sắp xếp trước đó như các phương pháp khác làm. Một điều nữa là nó sắp xếp tại chỗ, trong khi sắp xếp dường như tạo một bản sao.

Tôi đã sử dụng nó để viết một hàm xếp hạng, xếp hạng danh sách các lớp trong đó mỗi đối tượng nằm trong một nhóm và có chức năng cho điểm, nhưng bạn có thể thêm bất kỳ danh sách các thuộc tính nào. Lưu ý rằng không giống như lambda, mặc dù hackish sử dụng lambda để gọi một setter. Phần xếp hạng sẽ không hoạt động cho một loạt các danh sách, nhưng sắp xếp sẽ.

#First, here's  a pure list version
my_sortLambdaLst = [lambda x,y:cmp(x[0], y[0]), lambda x,y:cmp(x[1], y[1])]
def multi_attribute_sort(x,y):
    r = 0
    for l in my_sortLambdaLst:
        r = l(x,y)
        if r!=0: return r #keep looping till you see a difference
    return r

Lst = [(4, 2.0), (4, 0.01), (4, 0.9), (4, 0.999),(4, 0.2), (1, 2.0), (1, 0.01), (1, 0.9), (1, 0.999), (1, 0.2) ]
Lst.sort(lambda x,y:multi_attribute_sort(x,y)) #The Lambda of the Lambda
for rec in Lst: print str(rec)

Đây là một cách để xếp hạng danh sách các đối tượng

class probe:
    def __init__(self, group, score):
        self.group = group
        self.score = score
        self.rank =-1
    def set_rank(self, r):
        self.rank = r
    def __str__(self):
        return '\t'.join([str(self.group), str(self.score), str(self.rank)]) 


def RankLst(inLst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank)):
    #Inner function is the only way (I could think of) to pass the sortLambdaLst into a sort function
    def multi_attribute_sort(x,y):
        r = 0
        for l in sortLambdaLst:
            r = l(x,y)
            if r!=0: return r #keep looping till you see a difference
        return r

    inLst.sort(lambda x,y:multi_attribute_sort(x,y))
    #Now Rank your probes
    rank = 0
    last_group = group_lambda(inLst[0])
    for i in range(len(inLst)):
        rec = inLst[i]
        group = group_lambda(rec)
        if last_group == group: 
            rank+=1
        else:
            rank=1
            last_group = group
        SetRank_Lambda(inLst[i], rank) #This is pure evil!! The lambda purists are gnashing their teeth

Lst = [probe(4, 2.0), probe(4, 0.01), probe(4, 0.9), probe(4, 0.999), probe(4, 0.2), probe(1, 2.0), probe(1, 0.01), probe(1, 0.9), probe(1, 0.999), probe(1, 0.2) ]

RankLst(Lst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank))
print '\t'.join(['group', 'score', 'rank']) 
for r in Lst: print r
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.