[Newbie] How to output dictionary sorted on content (not keys)

Alex Martelli aleax at aleax.it
Fri Jul 5 11:45:53 EDT 2002


Achim Domma wrote:

> "Eric Brunel" <eric.brunel at pragmadev.com> wrote in message
> news:ag1fj0$inr$1 at wanadoo.fr...
>> I don't which one of these two or Achim's one is the most efficient.
> 
> I also don't know, but you can make it in one line:
> 
> for count,name in dict([ (count,name) for name,count in
> myDict.items() ]).items(): print count,name
> 
> wether this is good code or not is another question ... ;-)
> 
> Achim

I'm not sure what you believe you're achieving with this long
line.  The items of any dictionary are in arbitrary order, as
defined by the implementation's needs for speed (which dictate
dict implementation by hashing).  So there's no useful defined
purpose in making a dict out of a sequence of pairs and then
immediately looping on that dict's items.  Maybe you did some
tests and it seemed to you that this changed the ordering in
some useful way, rather than just replacing one arbitrary order
with another one fully as arbitrary...?

If you want to output a histogram in ascending count order
with a single long statement, this is indeed definitely feasible,
and it's just as definitely a horrible idea.  But, purely for
instructional purposes, here's how one could work it out...:

[approach 1]  the crux of the matter is being able to make
a .sort call (which sorts in-place and returns None) and
THEN iterate on the now-sorted list.  Unfortunately given
the one-statement constraint we can't use an ordinary
assignment to give a name to the list, sort it, and then
loop on it.  But, list comprehensions ALSO bind names, so...:

for count, name in (
    [ None for x in ( 
        [(count, name) for name, count in myDict.items()],)
      if 0] or
    x.sort() or
    x): print count, name

Basically this trick uses 'or' to join expressions which
are mostly acting as para-statements but returning false
values -- None for x.sort(), an artificial empty list for
[ 1 for x in ... if 0 ] -- with side effects (sorting x
for x.sort(), binding name x for the list comprehension).

In other words. this dirty trick is a pretty general one,
which doesn't make it any less dirty, of course.


Approach 2 is (to me) a bit more interesting but less
general.  You could say it relies on a bin-sort rather
than Python's normal (and excellent) samplesort.  A
dict is clearly a nice way to do a bin-sort -- except
that you lack an immediately obvious way to walk in a
sorted way over the bins themselves.  No problem --
min and max are there just for you.

So here's approach 2 in a clear, multistatement way:

auxdict = {}
for name,count in myDict.iteritems():
    auxdict.setdefault(count, []).append(name)
for key in range(min(auxdict.keys()), max(auxdict.keys())+1):
    for name in auxdict.get(key, []):
        print key, name

Pretty clear idea, I hope?  So, we just need to wrap this
up into a single statement, e.g.:

for k, n in [ (key, name) 
  for auxdict in ( {}, )
  for x in ( [ auxdict.setdefault(count, []).append(name)
             for name, count in myDict.items() ], )
  for key in range(min(auxdict.keys()), max(auxdict.keys())+1)
  for name in auxdict.get(key, [])
]: print k, n


Now, I don't really know why people are so obsessed about
wrapping things up as "a single statement" rather than
going for speed, clarity, concision, elegance, and all
other aspects of code that can sensibly be considered
good ones.  But anyway, if "wrapping up into a single
statement" is what rocks your boat, for whatever reason,
you can hardly complain that Python doesn't LET you do
it.  It may be silly, but then, some of Monty Python's
routines were also silly, but still hilarious, no?


Alex




More information about the Python-list mailing list