0

I have a dictionary and I need to print the contents of dictionary first sorted by "value" if there is any value tie then I need to use "key" as a tie breaker so that the keys that have the identical values will be printed in alphabetic order.

final = OrderedDict(sorted(mydict.items(), key=lambda x: (x[1], x[0]), reverse=True))

for key, value in final.items():
    print(f'{key} : {value}')

but when the print command runs, it will display the output like the identical values have not been sorted as expected:

Action : 3
Romance : 2
Horror : 2
History : 2
Comedy : 2
Adventure : 1

The way it's supposed to print must be like:

Action : 3
Comedy : 2
History : 2
Horror : 2
Romance : 2
Adventure : 1

Any thoughts about how can I correct this issue?

  • 1
    You used `reverse=True`, so the tiebreakers are reverse sorted as well. – chepner Aug 28 '22 at 16:12
  • 2
    You can use the negative number trick and use `key=lambda x: (-x[1], x[0])` instead of using `reverse=True`. – chepner Aug 28 '22 at 16:14
  • As mentioned by @chepner the problem is your expectation, so to correct this issue just change your expectation. – Claudio Aug 28 '22 at 16:17
  • Does this answer your question? [Python sorting by multiple criteria](https://stackoverflow.com/questions/20145842/python-sorting-by-multiple-criteria) – Claudio Aug 28 '22 at 18:33

4 Answers4

1

You want to reverse the sort for x[1] only so don't pass reverse=True here, instead use negative number trick:

final = OrderedDict(sorted(mydict.items(), key=lambda x: (-x[1], x[0])))
zero
  • 28
  • 6
0
{k: v for k, v in sorted(mydict.items(), key=lambda item: (-item[1], item[0]), reverse=False)}
BloomShell
  • 833
  • 1
  • 5
  • 20
  • A needlessly convoluted solution. Refer to chepner's comment under the question. – Jeffrey Ram Aug 28 '22 at 16:22
  • 1
    @JeffreyRam This is exactly what I was referring to; the `reverse` argument can simply be dropped instead of explicitly passing `False`. – chepner Aug 28 '22 at 16:22
  • `{k: v for k, v in }` and `reverse=False` are unnecessary. – Jeffrey Ram Aug 28 '22 at 16:29
  • 1
    The dict comprehension is necessary if you aren't calling `dict` (though passing the sorted list to `dict` is *simpler*). – chepner Aug 28 '22 at 16:31
  • `final = OrderedDict(sorted(mydict.items(), key=lambda item: (-item[1], item[0])))` works just as fine. Refer to OP's code sample. – Jeffrey Ram Aug 28 '22 at 16:32
  • 1
    @JeffreyRam imo using built-in functionalities is better whenever possible for reproducibility and optimization/performance of code. – BloomShell Aug 28 '22 at 16:40
  • Introducing pieces of codes that strays away from OP's style of coding without any explanation whatsoever is always a bad idea. An **unlikely bad scenario** would be for OP to directly implement this piece inside OrderedDict: `final = OrderedDict({k: v for k, v in sorted(mydict.items(), key=lambda item: (-item[1], item[0]), reverse=False)})`. – Jeffrey Ram Aug 28 '22 at 16:47
0

reverse is applied after the sequence has been sorted according to key. You only want the comparison on x[1] to be reversed, not the final ordering. I would use functools.cmp_to_key to convert a comparison function to a key function.

from functools import cmp_to_key

# Redefined from Python 2 since Python 3 dropped it
def cmp(x, y):
    return -1 if x < y else 0 if x == y else 1

def genre_sort(x, y):
    # A bigger count is a smaller genre; otherwise, sort alphabetically
    # by name
    return cmp(y[1], x[1]) or cmp(x[0], y[0])
    
final = OrderedDict(sorted(mydict.items(), key=cmp_to_key(genre_sort)))
chepner
  • 497,756
  • 71
  • 530
  • 681
-1

Why this answer when there are already three other ones?

In my opinion none of the other answers exposed the core of the problem described in the question, which is by the way already sufficiently answered here: https://stackoverflow.com/questions/20145842/python-sorting-by-multiple-criteria and here https://stackoverflow.com/questions/55866762/how-to-sort-a-list-of-strings-in-reverse-order-without-using-reverse-true-parame making it a candidate for becoming closed as a duplicate.

The feature of Python allowing to have a solution to the problem of sorting columns in different sorting orders is the fact that

Python’s sort is stable allowing you to use consecutive sorts, using the rightmost criteria first, then the next, etc. ( Martijn Pieters ).

That Python's sort algorithm is stable means that equal elements keep their relative order. So you can use a first sort on the second element (sorting in ascending order) and then sort again, on only the first element and in reverse order.

I have changed the dictionary listed in the question to having only string values what will not allow to use the 'trick' with negative number values in the key function to achieve the desired result to demonstrate what is said above.

The code below:

mydict = { 
    'Romance'   : '2', 
    'Adventure' : '1', 
    'Action'    : '3', 
    'Horror'    : '2', 
    'History'   : '2',
    'Comedy'    : '2',
}
mylist = list(mydict.items())
print(mylist)
print()
mylist = sorted(mylist)
print(mylist)
mslist = sorted(mylist, key=lambda x: (x[1]), reverse=True)
print(mslist)
from collections import OrderedDict
final = OrderedDict(mslist)
for key, value in final.items():
    print(f'  {key:10} : {value}')

creating following output:

[('Romance', '2'), ('Adventure', '1'), ('Action', '3'), ('Horror', '2'), ('History', '2'), ('Comedy', '2')]

[('Action', '3'), ('Adventure', '1'), ('Comedy', '2'), ('History', '2'), ('Horror', '2'), ('Romance', '2')]
[('Action', '3'), ('Comedy', '2'), ('History', '2'), ('Horror', '2'), ('Romance', '2'), ('Adventure', '1')]
  Action     : 3
  Comedy     : 2
  History    : 2
  Horror     : 2
  Romance    : 2
  Adventure  : 1

demonstrates what I am speaking about here.

From:

[('Action', '3'), ('Adventure', '1'), ('Comedy', '2'), ('History', '2'), ('Horror', '2'), ('Romance', '2')]
[('Action', '3'), ('Comedy', '2'), ('History', '2'), ('Horror', '2'), ('Romance', '2'), ('Adventure', '1')]

can be seen that the second sort moves only '('Adventure', '1')' keeping the order of all the other items.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Claudio
  • 7,474
  • 3
  • 18
  • 48