'Find an analogue for the Python 2.7 sorting behavior in Python 3.x [duplicate]
In Python 2.7 there was an ability to use the sorted function with a list that contains different types, e.g. string, tuple, integer, like this:
some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]
>>> sorted(some_list)
[1, [2, 3, 4], 'a', 'b', ('c', 'd')]
However, if we try to do something like this in Python 3, we will get an exception:
some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]
>>> sorted(some_list)
TypeError: '<' not supported between instances of 'int' and 'str'
What's the easiest way to mimic the behavior of the sorted function from python2.7? I understand that I can just write my own method for this purposes, but maybe a similar functionality was implemented in some library or moved to another specific built-in function?
Solution 1:[1]
You need to supply an explicit key to sort or sorted, and the key should explain how to compare elements of different types.
A quick solution is to replace element x with a tuple (type(x).__name__, x) so that elements are grouped by type first, and only compared if they have the same type:
some_list = ['a', [4, 5, 6], 3, 2.0, 'b', 1, 1.5, ('c', 'd'), [2, 3, 4]]
some_list.sort(key=lambda x: (type(x).__name__, x))
print(some_list)
# [1.5, 2.0, 1, 3, [2, 3, 4], [4, 5, 6], 'a', 'b', ('c', 'd')]
Note how the integers were separated from the floats. If you don't want that, you need a more complex key. I give an example at the bottom of this post.
You could try other keys, which will result in different orders:
key=strorkey=repr: Brutally convert everything to string before comparison. This has the downside that numbers are compared lexicographically, so that 1 < 10 < 2.key=lambda x: tuple(more_itertools.collapse(x))this flattens everything into tuples, so that a single number is equivalent to a list of one number, and a list of lists of lists is equivalent to a simple list. But it will still crash if trying to compare numbers with strings.- Some hybrid method of the two solutions above. You can declare a complex key like any function, using the
defkeyword. - Try comparing the elements with their usual comparison, then if it raises an exception, convert the elements into strings and compare again:
from functools import cmp_to_key
def cmp(a,b):
try:
return (a > b) - (a < b)
except TypeError:
a, b = str(a), str(b)
return (a > b) - (a < b)
k = cmp_to_key(cmp)
some_list = ['a', [4, 5, 6], 3, 2.0, 'b', 1, 1.5, ('c', 'd'), [2, 3, 4]]
some_list.sort(key=k)
print(some_list)
# [('c', 'd'), 1, 1.5, 2.0, 3, [2, 3, 4], [4, 5, 6], 'a', 'b']
Solution 2:[2]
You ought to be able to get what you're after with an appropriate key function. E.g.:
>>> some_list = ['a', 'b', 1, ('c', 'd'), [2, 3, 4]]
>>> sorted(some_list, key=lambda i: str(i[0] if hasattr(i, "__getitem__") else i))
[1, [2, 3, 4], 'a', 'b', ('c', 'd')]
The details of the key will depend on exactly how you want to handle comparisons between items of differing types -- the example above assumes you want to use string-based comparison, but there might be other methods.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Samwise |
