3

I'm new to Python and I'm currently working on solving problems to improve my coding skills. I'm working on a question where I need to stable sort a dictionary in python. Please find the details below:

Input:

1 2
16 3
11 2
20 3
3 5
26 4
7 1
22 4

The above input, I have added into two lists k and v:

k = ['1', '16', '11', '20', '3', '26', '7', '22']
v = ['2', '3', '2', '3', '5', '4', '1', '4']

I have added both the lists into a dictionary to have it as a key-value pair. I have used OrderDict because I want to have the elements in the same order as they are in the input.

from collections import OrderedDict
d = OrderedDict(zip(k, v))

Now, I need to sort the dictionary d in the reverse order with respect to values. (I actually have to do a stable sort and since sorted in python is a stable sort, I have used that. Source: Here) For that:

s = sorted(d, key = itemgetter(1), reverse=True)

Expected Output:

 3 5
26 4
22 4
16 3
20 3
1 2
11 2
7 1

But after I implemented the above sorted function, I'm not able to get the expected output. I get IndexError: string index out of range

Can someone tell me where am I doing wrong. Is my approach wrong or flow is wrong? Could you please tell me why I'm not able to get the output as expected. Thanks in advance. Any help would be much appreciated.

Community
  • 1
  • 1
sdgd
  • 723
  • 1
  • 17
  • 38

4 Answers4

6

Here is one way to do it:

>>> sorted_kv = sorted(d.items(), key=lambda (k,v):int(v), reverse=True)
>>> OrderedDict(sorted_kv)
OrderedDict([('3', '5'), ('26', '4'), ('22', '4'), ('16', '3'), ...

This takes the key/value pairs from the dictionary, sorts them, and creates a new ordered dictionary with the required ordering.

The key= argument to sorted() specifies that the pairs are to be sorted according to the numeric value of the second item.

The reason I needed to call int() is that your dictionary keeps both the keys and the values as strings. Sorting them as-is will work, but will produce the lexicographic ordering instead of the numeric one.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • Thanks for the detailed explanation. I have a couple of queries - 1. why does the dictionary keep the keys and values as strings even though all the values are integers. 2. does having just `d` in place of `d.items` make any difference? what's the purpose. Sorry if its sounds silly, but I just want to understand better. – sdgd Feb 17 '16 at 17:30
  • @Dev 1) Python is strongly typed. That means it won't silently convert between types; you must force it to by casting. There are some places where this is loosened a bit (for instance, mathematical operations between different types). Even though the strings only contain numeric characters, they are still strings and will never be treated as anything different unless you explicitly cast them to something else. 2) Iterating over just `d` will only give you the keys. `d.items()` returns tuples of (key, value). Basically, it saves you an extra dictionary look up later. – eestrada Feb 17 '16 at 19:37
5

You forgot to access the items of your dictionary by using .items() (Python3) or .iteritems() (Python2).

In addition you need to import the operator module in order to use itemgetter().

So the code would look like:

import operator
from collections import OrderedDict

k = ['1', '16', '11', '20', '3', '26', '7', '22']
v = ['2', '3', '2', '3', '5', '4', '1', '4']

d = OrderedDict(zip(k, v))

out = sorted(d.items(), key=operator.itemgetter(1), reverse=True)

Where list out is like:

[('3', '5'), ('26', '4'), ('22', '4'), ('16', '3'), ('20', '3'), ('1', '2'), ('11', '2'), ('7', '1')]

In order to print you can access each tuple of the list by using:

for i,k in out:
    print(i,k) 

Which gives the desired output:

3 5
26 4
22 4
16 3
20 3
1 2
11 2
7 1

I have written an example of the code given above which can be found at ideone.com.

albert
  • 8,027
  • 10
  • 48
  • 84
  • thank you, i did try that while i was validating 'but the i was getting `string index out of range` error :(. and the output what you have given is not the expected output. I gave the import operator – sdgd Feb 17 '16 at 17:32
  • The output list `out` is in your desired order. If you want to print it you simply need to access each tuple out of the list. Please see me updated answer. – albert Feb 17 '16 at 17:34
  • thank you. it worked. sorry for not getting you earlier. can you please tell me what happens if i dont use `d.items()` and just give `d`? will it not iterate the elements ? – sdgd Feb 17 '16 at 17:45
  • 1
    Take a look at this [example](http://ideone.com/A36Qk6) and in the docs [here](https://docs.python.org/3.5/library/stdtypes.html#dict-views) and [here](https://docs.python.org/3.5/library/stdtypes.html#dict.items). – albert Feb 17 '16 at 17:50
1

This is another way to do it:

from collections import OrderedDict

k = ['1', '16', '11', '20', '3', '26', '7', '22']
v = ['2', '3', '2', '3', '5', '4', '1', '4']

d = OrderedDict(zip([int(x) for x in k], [int(y) for y in v]))  # convert from string to int
sorted_items = sorted(d.items())
sorted_items.reverse()
s = OrderedDict(sorted_items)  # new sorted ordered dict
Bob Dylan
  • 1,773
  • 2
  • 16
  • 27
0

Here is my spoon of thoughts on that task:

k = ['1', '16', '11', '20', '3', '26', '7', '22']
v = ['2', '3', '2', '3', '5', '4', '1', '4']

# create a dictionary
d = dict([(k[ind], v[ind]) for ind in range(0, len(k))])

for order in sorted(d, key=d.__getitem__, reverse=True):
    print ("{}: {}".format(order, d[order]))

Output:

3: 5
26: 4
22: 4
16: 3
20: 3
11: 2
1: 2
7: 1

But I noticed, that in your expected output '1' goes before '11. Is there a reason for that?

Vadim
  • 633
  • 1
  • 8
  • 17