Sort a list of tuples by second value, reverse=True and then by key, reverse=False

Posted on

Question :

Sort a list of tuples by second value, reverse=True and then by key, reverse=False

I need to sort a dictionary by first, values with `reverse=True`, and for repeating values, sort by keys, `reverse=False`

So far, I have this

``````dict = [('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]
sorted(dict.items(), key=lambda x: (x[1],x[1]), reverse=True)
``````

which returns…

``````[('B', 3), ('A', 2), ('J', 1), ('I', 1), ('A', 1)]
``````

but I need it to be:

``````[('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]
``````

as you can see, when values are equal, I can only sort the key in a decreasing fashion as specified… But how can I get them to sort in an increasing fashion?

The following works with your input:

``````d = [('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]
sorted(d,key=lambda x:(-x[1],x[0]))
``````

Since your “values” are numeric, you can easily reverse the sort order by changing the sign.

In other words, this sort puts things in order by value (`-x[1]`) (the negative sign puts big numbers first) and then for numbers which are the same, it orders according to key (`x[0]`).

If your values can’t so easily be “negated” to put big items first, an easy work-around is to sort twice:

``````from operator import itemgetter
d.sort(key=itemgetter(0))
d.sort(key=itemgetter(1),reverse=True)
``````

which works because python’s sorting is stable.

``````In [4]: l = [('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]

In [5]: sorted(l, key=lambda (x,y):(-y,x))
Out[5]: [('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]
``````

you can use `collections.defaultdict`:

``````In [48]: from collections import defaultdict

In [49]: dic=[('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]

In [50]: d=defaultdict(list)

In [51]: for x,y in dic:
d[y].append(x)
d[y].sort()          #sort the list
``````

now `d` is something like:

`````` defaultdict(<type 'list'>, {1: ['A', 'I', 'J'], 2: ['A'], 3: ['B']}
``````

i.e. A new `dict` with `1,2,3...` as keys and corresponding alphabets stored in lists as values.

Now you can iterate over the `sorted(d.items)` and get the desired result using `itertools.chain()` and `itertools.product()`.

``````In [65]: l=[ product(y,[x]) for x,y in sorted(d.items(),reverse=True)]

In [66]: list(chain(*l))
Out[66]: [('B', 3), ('A', 2), ('A', 1), ('I', 1), ('J', 1)]
``````