views:

454

answers:

5

I have dict in Python with keys of the following form:

mydict = {'0'     : 10,
          '1'     : 23,
          '2.0'   : 321,
          '2.1'   : 3231,
          '3'     : 3,
          '4.0.0' : 1,
          '4.0.1' : 10,
          '5'     : 11,
          # ... etc
          '10'    : 32,
          '11.0'  : 3,
          '11.1'  : 243,
          '12.0'  : 3,
          '12.1.0': 1,
          '12.1.1': 2,
          }

Some of the indices have no sub-values, some have one level of sub-values and some have two. If I only had one sub-level I could treat them all as numbers and sort numerically. The second sub-level forces me to handle them all as strings. However, if I sort them like strings I'll have 10 following 1 and 20 following 2.

How can I sort the indices correctly?

Note: What I really want to do is print out the dict sorted by index. If there's a better way to do it than sorting it somehow that's fine with me.

+12  A: 

You can sort the keys the way that you want, by splitting them on '.' and then converting each of the components into an integer, like this:

sorted(mydict.keys(), key=lambda a:map(int,a.split('.')))

which returns this:

['0',
 '1',
 '2.0',
 '2.1',
 '3',
 '4.0.0',
 '4.0.1',
 '5',
 '10',
 '11.0',
 '11.1',
 '12.0',
 '12.1.0',
 '12.1.1']

You can iterate over that list of keys, and pull the values out of your dictionary as needed.

You could also sort the result of mydict.items(), very similarly:

sorted(mydict.items(), key=lambda a:map(int,a[0].split('.')))

This gives you a sorted list of (key, value) pairs, like this:

[('0', 10),
 ('1', 23),
 ('2.0', 321),
 ('2.1', 3231),
 ('3', 3),
 # ...
 ('12.1.1', 2)]
Ian Clelland
And of course you don't need to use a lambda, you can define a function the usual way and pass in the name.
steveha
Faster than the comparator version (fewer conversions/function calls).
bobince
+2  A: 

Python's sorting functions can take a custom compare function, so you just need to define a function that compares keys the way you like:

def version_cmp(a, b):
  '''These keys just look like version numbers to me....'''
  ai = map(int, a.split('.'))
  bi = map(int, b.split('.'))
  return cmp(ai, bi)

for k in sorted(mydict.keys(), version_cmp):
  print k, mydict[k]

In this case you should better to use the key parameter to sorted(), though. See Ian Clelland's answer for an example for that.

sth
It's better to supply the key function as Ian does. The key function gets called once for each element, the cmp function gets called everytime the sort does a compare.
gnibbler
@gnibbler: Yeah, I realized that after reading Ian's answer... I was thinking about deleting my answer, but I guess it might be helpful for someone that needs a more complicated compare that can't be done by passing a simple `key`.
sth
A: 

I would do a search on "sorting a python dictionary" and take a look at the answers. I would give PEP-265 a read as well. The sorted() function is what you are looking for.

D.Shawley
A: 

As an addendum to Ian Clelland's answer, the map() call can be replaced with a list comprehension... if you prefer that style. It may also be more efficient (though negligibly in this case I suspect).

sorted(mydict.keys(), key=lambda a: [int(i) for i in a.split('.')])

Rob Cowie
+1  A: 

For fun & usefulness (for googling ppl, mostly):

f = lambda i: [int(j) if re.match(r"[0-9]+", j) else j for j in re.findall(r"([0-9]+|[^0-9]+)", i)]
cmpg = lambda x, y: cmp(f(x), f(y))

use as sorted(list, cmp=cmpg). Additionally, regexes might be pre-compiled (rarely necessary though, actually, with re module's caching). And, it may be (easily) modified, for example, to include negative values (add -? to num regex, probably) and/or to use float values.

It might be not very efficient, but even with that it's quite useful.

And, uhm, it can be used as key= for sorted() too.