เรียงรายการตามคุณลักษณะหลาย ๆ


457

ฉันมีรายการ:

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

ถ้าผมต้องการที่จะเรียงลำดับตามองค์ประกอบหนึ่งพูดสูง / s = sorted(s, key = itemgetter(1))องค์ประกอบระยะสั้นผมสามารถทำมันได้ผ่านทาง

หากฉันต้องการเรียงลำดับทั้งสูง / สั้นและสีฉันสามารถเรียงลำดับสองครั้งหนึ่งครั้งสำหรับแต่ละองค์ประกอบ แต่มีวิธีที่เร็วกว่าหรือไม่



8
ถ้าคุณใช้tuplessortแทนรายการประเภทคำสั่งหลามโดยรายการจากซ้ายไปขวาเมื่อคุณเรียกใช้ นั่นคือsorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)].
Mateen Ulhaq

คำตอบ:


773

คีย์สามารถเป็นฟังก์ชันที่คืนค่า tuple:

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

หรือคุณสามารถใช้งานได้เหมือนกันitemgetter(ซึ่งเร็วกว่าและหลีกเลี่ยงการเรียกฟังก์ชัน Python):

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

และสังเกตว่าที่นี่คุณสามารถใช้sortแทนการใช้sortedแล้วกำหนดใหม่:

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

20
เพื่อความสมบูรณ์จาก timeit: สำหรับฉันครั้งแรกให้เรา 6 ต่อวงและครั้งที่สอง 4.4 เราต่อวง
ไบรอัน Larsen

10
มีวิธีการเรียงลำดับแรกขึ้นและลงสอง? (สมมติว่าทั้งสองแอ็ตทริบิวต์เป็นสตริงดังนั้นจึงไม่มีแฮ็กเพิ่ม-สำหรับจำนวนเต็ม)
Martin Thoma

73
ถ้าฉันต้องการสมัครrevrse=Trueเท่านั้นจะx[1]เป็นไปได้ไหม
Amyth

28
@moose, @Amyth เพื่อย้อนกลับไปที่หนึ่งคุณลักษณะเท่านั้นคุณสามารถเรียงลำดับได้สองครั้ง: อันดับแรกโดยอันดับรองs = sorted(s, key = operator.itemgetter(2))จากนั้นตามด้วยหลักs = sorted(s, key = operator.itemgetter(1), reverse=True)ไม่ใช่อุดมคติ แต่ใช้ได้ผล
tomcounsell

52
@Amyth -1หรืออีกทางเลือกหนึ่งหากที่สำคัญเป็นจำนวนที่จะทำให้มันกลับคุณสามารถหลายมันด้วย
Serge

37

ฉันไม่แน่ใจว่านี่เป็นวิธีการ pythonic ที่มากที่สุดหรือไม่ ... ฉันมีรายการของสิ่งอันดับที่ต้องเรียงลำดับที่ 1 โดยการลดค่าจำนวนเต็มและลำดับที่ 2 ตามลำดับตัวอักษร สิ่งนี้ต้องการการกลับเรียงลำดับจำนวนเต็ม แต่ไม่ใช่เรียงลำดับตัวอักษร นี่คือทางออกของฉัน: (ทันทีในการสอบ btw ฉันไม่ได้รู้ว่าคุณสามารถจัดเรียงฟังก์ชัน 'ซ้อน')

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
เนื่องจากลำดับที่ 2 เป็นตัวเลขมันจะทำงานเพื่อให้เหมือนb = sorted(a, key = lambda x: (-x[1], x[0]))ที่เห็นได้ชัดเจนขึ้นเกี่ยวกับเกณฑ์ที่ใช้ก่อน สำหรับประสิทธิภาพฉันไม่แน่ใจว่ามีคนต้องการเวลา
Andrei-Niculae Petre

5

หลายปีที่ผ่านมาในช่วงปลายไปงานเลี้ยง แต่ฉันต้องการทั้งการเรียงลำดับวันที่ 2 เกณฑ์และreverse=Trueการใช้งาน ในกรณีที่คนอื่นต้องการทราบวิธีการคุณสามารถห่อเกณฑ์ (ฟังก์ชั่น) ไว้ในวงเล็บ:

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

5

ดูเหมือนว่าคุณสามารถใช้แทนlist tupleสิ่งนี้กลายเป็นสิ่งสำคัญยิ่งกว่าที่ฉันคิดว่าเมื่อคุณโลภแอตทริบิวต์แทนที่จะเป็น 'ดัชนีเวทย์มนตร์' ของรายการ / ทูเปิล

ในกรณีของฉันฉันต้องการเรียงลำดับตามคุณลักษณะต่าง ๆ ของคลาสที่คีย์ขาเข้าเป็นสตริง ฉันต้องการเรียงลำดับที่แตกต่างกันในสถานที่ที่แตกต่างกันและฉันต้องการเรียงลำดับเริ่มต้นทั่วไปสำหรับชั้นผู้ปกครองที่ลูกค้ามีปฏิสัมพันธ์กับ; แค่ต้องแทนที่ 'แป้นการเรียงลำดับ' เมื่อฉันต้องการ 'จริง ๆ เท่านั้น' แต่ในวิธีที่ฉันสามารถเก็บไว้เป็นรายการที่ชั้นเรียนสามารถแบ่งปันได้

ดังนั้นก่อนอื่นฉันกำหนดวิธีใช้ตัวช่วย

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 ]

จากนั้นจะใช้มัน

# 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))

นี้จะใช้ฟังก์ชั่นแลมบ์ดาที่สร้างขึ้นเรียงลำดับรายการจากobject.attrAนั้นobject.attrBถือว่าobjectมีทะเยอทะยานที่สอดคล้องกับชื่อสตริงที่ให้ไว้ และกรณีที่สองจะจัดเรียงตามแล้ว object.attrCobject.attrA

นอกจากนี้ยังช่วยให้คุณสามารถเปิดเผยตัวเลือกการเรียงลำดับภายนอกเพื่อแบ่งปันโดยผู้บริโภคการทดสอบหน่วยหรือเพื่อให้พวกเขาบอกคุณว่าพวกเขาต้องการเรียงลำดับอย่างไรสำหรับการดำเนินการบางอย่างใน API ของคุณโดยเพียงแค่ให้รายการกับคุณ เชื่อมต่อพวกเขาเข้ากับการใช้งานด้านหลัง


ทำได้ดีมาก เกิดอะไรขึ้นถ้าแอตทริบิวต์ควรเรียงลำดับที่แตกต่างกัน? สมมติว่าควรจัดเรียง attrA จากน้อยไปมากและลดระดับ attrB หรือไม่ มีวิธีแก้ปัญหาอย่างรวดเร็วบนนี้หรือไม่ ขอบคุณ!
mhn_namak

1

ต่อไปนี้เป็นวิธีหนึ่ง: โดยทั่วไปคุณเขียนฟังก์ชันการเรียงลำดับใหม่เพื่อรับรายการฟังก์ชั่นการเรียงลำดับฟังก์ชั่นการจัดเรียงแต่ละรายการเปรียบเทียบคุณลักษณะที่คุณต้องการทดสอบในการทดสอบการเรียงลำดับแต่ละครั้งคุณดูและดูว่า ถ้าเป็นเช่นนั้นทำลายและส่งค่าตอบแทน คุณเรียกมันว่าโดยเรียกแลมบ์ดาของฟังก์ชั่นของรายการแลมบ์ดา

ข้อได้เปรียบของมันคือการส่งผ่านข้อมูลครั้งเดียวไม่ใช่การเรียงลำดับก่อนหน้าเช่นเดียวกับวิธีอื่น ๆ อีกสิ่งหนึ่งคือมันเรียงลำดับในขณะที่เรียงดูเหมือนจะทำสำเนา

ฉันใช้มันเพื่อเขียนฟังก์ชั่นอันดับซึ่งจัดอันดับรายการของคลาสที่แต่ละออบเจ็กต์อยู่ในกลุ่มและมีฟังก์ชั่นคะแนน แต่คุณสามารถเพิ่มรายการคุณลักษณะใด ๆ ได้ สังเกตว่าไม่เหมือนกับแลมบ์ดาแม้ว่าแฮ็กจะใช้แลมบ์ดาในการเรียกตัวตั้งค่า ส่วนอันดับไม่สามารถใช้ได้กับอาร์เรย์ของรายการ แต่การเรียงลำดับจะ

#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)

ต่อไปนี้เป็นวิธีจัดอันดับรายการของวัตถุ

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.