Sort a list by multiple attributes?

前端 未结 6 1982
后悔当初
后悔当初 2020-11-22 02:48

I have a list of lists:

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

If I wanted to sort by one e

相关标签:
6条回答
  • 2020-11-22 03:08

    There is a operator < between lists e.g.:

    [12, 'tall', 'blue', 1] < [4, 'tall', 'blue', 13]
    

    will give

    False
    
    0 讨论(0)
  • 2020-11-22 03:11

    Here's one way: You basically re-write your sort function to take a list of sort functions, each sort function compares the attributes you want to test, on each sort test, you look and see if the cmp function returns a non-zero return if so break and send the return value. You call it by calling a Lambda of a function of a list of Lambdas.

    Its advantage is that it does single pass through the data not a sort of a previous sort as other methods do. Another thing is that it sorts in place, whereas sorted seems to make a copy.

    I used it to write a rank function, that ranks a list of classes where each object is in a group and has a score function, but you can add any list of attributes. Note the un-lambda-like, though hackish use of a lambda to call a setter. The rank part won't work for an array of lists, but the sort will.

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

    Here's a way to rank a list of objects

    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
    
    0 讨论(0)
  • 2020-11-22 03:13

    I'm not sure if this is the most pythonic method ... I had a list of tuples that needed sorting 1st by descending integer values and 2nd alphabetically. This required reversing the integer sort but not the alphabetical sort. Here was my solution: (on the fly in an exam btw, I was not even aware you could 'nest' sorted functions)

    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)]
    
    0 讨论(0)
  • 2020-11-22 03:14

    A key can be a function that returns a tuple:

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

    Or you can achieve the same using itemgetter (which is faster and avoids a Python function call):

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

    And notice that here you can use sort instead of using sorted and then reassigning:

    s.sort(key = operator.itemgetter(1, 2))
    
    0 讨论(0)
  • 2020-11-22 03:18

    It appears you could use a list instead of a tuple. This becomes more important I think when you are grabbing attributes instead of 'magic indexes' of a list/tuple.

    In my case I wanted to sort by multiple attributes of a class, where the incoming keys were strings. I needed different sorting in different places, and I wanted a common default sort for the parent class that clients were interacting with; only having to override the 'sorting keys' when I really 'needed to', but also in a way that I could store them as lists that the class could share

    So first I defined a helper method

    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 ]
    

    then to use it

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

    This will use the generated lambda function sort the list by object.attrA and then object.attrB assuming object has a getter corresponding to the string names provided. And the second case would sort by object.attrC then object.attrA.

    This also allows you to potentially expose outward sorting choices to be shared alike by a consumer, a unit test, or for them to perhaps tell you how they want sorting done for some operation in your api by only have to give you a list and not coupling them to your back end implementation.

    0 讨论(0)
  • 2020-11-22 03:33

    Several years late to the party but I want to both sort on 2 criteria and use reverse=True. In case someone else wants to know how, you can wrap your criteria (functions) in parenthesis:

    s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)
    
    0 讨论(0)
提交回复
热议问题