0

Context:
In Python 3.9 sorting a list of most objects by a second list is easy, even if duplicates are present:

>>> sorted(zip([5, 5, 3, 2, 1], ['z', 'y', 'x', 'w', 'x']))
[(1, 'x'), (2, 'w'), (3, 'x'), (5, 'y'), (5, 'z')]

If this list to be sorted contains dictionaries, sorting generally goes fine:

>>> sorted(zip([3, 2, 1], [{'z':1}, {'y':2}, {'x':3}])) 
[(1, {'x': 3}), (2, {'y': 2}), (3, {'z': 1})]

Issue:
However, when the list to be sorted by contains duplicates, the following error occurs:

>>>sorted(zip([5, 5, 3, 2, 1], [{'z':1}, {'y':2}, {'x':3}, {'w': 4}, {'u': 5}])) 
*** TypeError: '<' not supported between instances of 'dict' and 'dict'

The issue sees pretty crazy to me: How do the values of the list to be sorted even affect the list to be sorted by?

Alternative solution:
One, not so elegant solution would be to get the dictionary objects from the list by index:

>>> sort_list = [5, 5, 3, 2, 1]    
>>> dicts_list = [{'z':1}, {'y':2}, {'x':3}, {'w': 4}, {'u': 5}]
>>> [dicts_list[i] for _, i in sorted(zip(sort_list, range(len(sort_list))))]
 [{'u': 5}, {'w': 4}, {'x': 3}, {'z': 1}, {'y': 2}]

Related Questions on StackOverflow:
Many similar questions have been raised on StackOverflow, related to

This specific case, especially including duplicates has not been discussed yet.

M.G.Poirot
  • 1,066
  • 2
  • 11
  • 22
  • 4
    You are sorting a list of *tuples*. To compare two tuples, you start by comparing their first elements (here, `int` values). If they are equal, you next compare their second elements (which are `dict`s). You need a custom comparison function that knows how to compare two `dict` values. – chepner May 24 '23 at 14:34

3 Answers3

5

The reason for this is the way tuple comparison works. Generally, if t1 and t2 are tuples, t1 < t2 if t1[0] < t2[0], however if t1[0] == t2[0], then it will compare t1[1] < t2[1] and so on. This means you can compare tuples even if they contain items that can't be compared to each other, as long as the comparison can differentiate items that come before it.

The standard way to solve this is to provide a key function that ignores the dictionary:

sorted(zip([5, 5, 3, 2, 1], [{'z':1}, {'y':2}, {'x':3}, {'w': 4}, {'u': 5}]), key=lambda item: item[0])
Jasmijn
  • 9,370
  • 2
  • 29
  • 43
1

In your second example:

sorted(zip([3, 2, 1], [{'z':1}, {'y':2}, {'x':3}]))

The sort never has to use the dictionaries in the comparison because the first item of the tuples produced by zip are never equal.

In the third example:

sorted(zip([5, 5, 3, 2, 1], [{'z':1}, {'y':2}, {'x':3}, {'w': 4}, {'u': 5}]))

The two zipped tuples that start with 5 need to use their second items (the dictionaries) in the comparison and, since dictionaries cannot be compared to determine which one is greater/smaller, the sort fails.

In order for this to work, you would have to provide a key function or lambda to help the sort determine how you want your dictionaries to be ordered. It could be many different ways such as comparing only on keys, on the largest value or key or some other convention (that's up to you, sort doesn't know)

Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

A variant of the @Jasmin answer using itemgetter instead of a lambda function to return the first element of each tuple (so ignoring the dictionnaries)

from operator import itemgetter
sorted(zip([5, 5, 3, 2, 1], [{'z':1}, {'y':2}, {'x':3}, {'w': 4}, {'u': 5}]),key=itemgetter(0)) 
manu190466
  • 1,557
  • 1
  • 9
  • 17