How to sort a list with two keys but one in reverse order?

21,619

Solution 1

Two keys will be used when we need to sort a list with two constraints: one in ascending order and the other in descending, in the same list or any

In your example,

sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))

you can sort entire list only in one order.

You can try these and check what's happening:

sortedList = sorted(myList, key = lambda y: (y[0].lower(), -y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), -y[1]))

Solution 2

You could create a reversor class and use it to decorate the key in question. This class could be used to reverse any field that is comparable.

class reversor:
    def __init__(self, obj):
        self.obj = obj

    def __eq__(self, other):
        return other.obj == self.obj

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

Use it like so:

sortedList = sorted(myList, key=lambda y: (y[0].lower(), reversor(y[1])))

Solution 3

Sometimes there is little alternative but to use a comparator function. There was a cmp argument to sorted from its introduction to 2.4, but it was removed from Python 3 in favour of the more efficient key function. In 3.2, cmp_to_key was added to functools; it creates keys from the original objects by wrapping them in an object whose comparison function is based on the cmp function. (You can see a simple definition of cmp_to_key at the end of the Sorting How-To

In your case, since lower-casing is relatively expensive, you might want to do a combination:

class case_insensitive_and_2nd_reversed:
    def __init__(self, obj, *args):
        self.first = obj[0].lower()
        self.second = obj[1]
    def __lt__(self, other):
        return self.first < other.first or self.first == other.first and other.second < self.second
    def __gt__(self, other):
        return self.first > other.first or self.first == other.first and other.second > self.second
    def __le__(self, other):
        return self.first < other.first or self.first == other.first and other.second <= self.second
    def __ge__(self, other):
        return self.first > other.first or self.first == other.first and other.second >= self.second
    def __eq__(self, other):
        return self.first == other.first and self.second == other.second
    def __ne__(self, other):
        return self.first != other.first and self.second != other.second

sortedList = sorted(myList, key = case_insensitive_and_2nd_reversed)

Solution 4

Method 1

A simple solution, but might not be the most efficient is to sort twice: the first time using the second element, the second using the first element:

sortedList = sorted(sorted(myList, key=lambda (a,b):b, reverse=True), key=lambda(a,b):a)

Or break down:

tempList = sorted(myList, key=lambda (a,b):b, reverse=True)
sortedList = sorted(tempList, key=lambda(a,b):a))

Method 2

If your elements are numbers, you can cheat a little:

sorted(myList, key=lambda(a,b):(a,1.0/b))

Method 3

I recommend against this approach as it is messy and the cmp keyword is not available in Python 3.

Another approach is to swap the elements when comparing the elements:

def compare_func(x, y):
    tup1 = (x[0], y[1])
    tup2 = (x[1], y[0])
    if tup1 == tup2:
        return 0
    elif tup1 > tup2:
        return 1
    else:
        return -1

sortedList = sorted(myList, cmp=compare_func)

Or, using lambda to avoid writing function:

sortedList = sorted(
    myList,
    cmp=lambda (a1, b1), (a2, b2): 0 if (a1, b2) == (a2, b1) else 1 if (a1, b2) > (a2, b1) else -1
    )

Solution 5

maybe elegant but not efficient way:

reverse_key = functools.cmp_to_key(lambda a, b: (a < b) - (a > b))
sortedList = sorted(myList, key = lambda y: (reverse_key(y[0].lower()), y[1]))
Share:
21,619
Stanisław Łabądź
Author by

Stanisław Łabądź

Updated on January 23, 2022

Comments

  • Stanisław Łabądź
    Stanisław Łabądź over 2 years

    I was wondering what would be a Pythonic way of sorting a list of tuples by two keys whereby sorting with one (and only one) key would be in a reverse order and sorting with the the other would be case insensitive. More specifically, I have a list containing tuples like:

    myList = [(ele1A, ele2A),(ele1B, ele2B),(ele1C, ele2C)]
    

    I can use the following code to sort it with two keys:

    sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))
    

    To sort in reverse order I can use

    sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]), reverse = True)
    

    But this would sort in a reverse order with two keys.

  • Tim Givois
    Tim Givois over 4 years
    This solution can be used with strings or other objects. Its a cleaner solution than the one that's tagged as best solution.
  • kaya3
    kaya3 over 4 years
    Better decorate this with functools.total_ordering: docs.python.org/3/library/…
  • black panda
    black panda over 4 years
    Total ordering is unnecessary for use as a key parameter in the sorted function. You only need == and <
  • chrisinmtown
    chrisinmtown almost 3 years
    This only works for lists of numbers? The OP didn't say what type the elements are.
  • Colin 't Hart
    Colin 't Hart over 2 years
    The answer by @black-panda works for all data types -- and object type that is comparable -- and is a much much better answer.
  • wjandrea
    wjandrea about 2 years
    Method 2 doesn't work for zero; it raisesZeroDivisionError. I think you meant -b.
  • Timothy C. Quinn
    Timothy C. Quinn about 2 years
    Definitely the best answer all around. For n00bs, if you are dealing with list of dicts, use y[<key>]. For list of objects use y.<property>.
  • juanpa.arrivillaga
    juanpa.arrivillaga about 2 years
    what's with all the rigamarole around None? Seems that shouldn't be dealt with at all in this method.
  • Timothy C. Quinn
    Timothy C. Quinn about 2 years
    If the data has a None, you will get an error like TypeError: '>' not supported between instances of 'NoneType' and '<type>'. That None check gets around that issue and handles them gracefully.
  • black panda
    black panda about 2 years
    There is no reason to work around None for the general purpose. The 'sorted' method itself does not accept None, for the very reason that None is not comparable. That is a special case situation that has nothing to do with the question or the answer, in my opinion.