How to sort a list with two keys but one in reverse order?
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]))
Stanisław Łabądź
Updated on January 23, 2022Comments
-
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 over 4 yearsThis solution can be used with strings or other objects. Its a cleaner solution than the one that's tagged as best solution.
-
kaya3 over 4 yearsBetter decorate this with
functools.total_ordering
: docs.python.org/3/library/… -
black panda over 4 yearsTotal ordering is unnecessary for use as a key parameter in the sorted function. You only need == and <
-
chrisinmtown almost 3 yearsThis only works for lists of numbers? The OP didn't say what type the elements are.
-
Colin 't Hart over 2 yearsThe answer by @black-panda works for all data types -- and object type that is comparable -- and is a much much better answer.
-
wjandrea about 2 yearsMethod 2 doesn't work for zero; it raises
ZeroDivisionError
. I think you meant-b
. -
Timothy C. Quinn about 2 yearsDefinitely the best answer all around. For n00bs, if you are dealing with list of dicts, use
y[<key>]
. For list of objects usey.<property>
. -
juanpa.arrivillaga about 2 yearswhat's with all the rigamarole around
None
? Seems that shouldn't be dealt with at all in this method. -
Timothy C. Quinn about 2 yearsIf 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 about 2 yearsThere 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.