17

How do I sort a list of strings by key=len first then by key=str? I've tried the following but it's not giving me the desired sort:

>>> ls = ['foo','bar','foobar','barbar']
>>> 
>>> for i in sorted(ls):
...     print i
... 
bar
barbar
foo
foobar
>>>
>>> for i in sorted(ls, key=len):
...     print i
... 
foo
bar
foobar
barbar
>>> 
>>> for i in sorted(ls, key=str):
...     print i
... 
bar
barbar
foo
foobar

I need to get:

bar
foo
barbar
foobar
alvas
  • 115,346
  • 109
  • 446
  • 738

4 Answers4

27

Define a key function that returns a tuple in which the first item is len(str) and the second one is the string itself. Tuples are then compared lexicographically. That is, first the lengths are compared; if they are equal then the strings get compared.

In [1]: ls = ['foo','bar','foobar','barbar']

In [2]: sorted(ls, key=lambda s: (len(s), s))
Out[2]: ['bar', 'foo', 'barbar', 'foobar']
root
  • 76,608
  • 25
  • 108
  • 120
  • just 2c if performance matters: a standalone function should be much faster than `lambda`. – bereal May 13 '13 at 06:25
  • 2
    @Eric - Duh... After testing it - I have to say there really isn't any performance difference. – root May 13 '13 at 07:02
  • 1
    Hm after testing it, I revoke my comment, no difference indeed. Was some bias of mine. – bereal May 13 '13 at 07:54
12

The answer from root is correct, but you don't really need a lambda:

>>> def key_function(x):
        return len(x), str(x)

>>> sorted(['foo','bar','foobar','barbar'], key=key_function)
['bar', 'foo', 'barbar', 'foobar']

In addtion, there is a alternate approach takes advantage of sort stability which lets you sort in multiple passes (with the secondary key first):

>>> ls = ['foo','bar','foobar','barbar']
>>> ls.sort(key=str)                       # secondary key
>>> ls.sort(key=len)                       # primary key

See the Sorting HOWTO for a good tutorial on Python sorting techniques.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 9
    Well, I would say there is no need for a full blown function here :) (BTW, you have a missing `, key=key_function)`) – root May 13 '13 at 06:38
  • 3
    When faced with a beginner question, I typically won't use a *lambda* in the answer and instead will use the most plain formulation of the answer. If someone is asking this question about sorting and key functions, then it suggest that they are not already comfortable with *lambda*. – Raymond Hettinger May 13 '13 at 07:56
  • 1
    Point taken. I suppose I read it as of the use of *lambdas* in this context should be somehow discouraged in general. – root May 13 '13 at 08:18
2

If you don't want to use lambda:

from operator import itemgetter
ls = ['foo','bar','foobar','barbar']
print sorted([ [x,len(x)] for x in ls ] ,key=itemgetter(1,0))
# print [s[0] for s in sorted([ [x,len(x)] for x in ls ] ,key=itemgetter(1,0))]
perreal
  • 94,503
  • 21
  • 155
  • 181
0

Another form that works without a lambda:

>>> [t[1] for t in sorted((len(s),s) for s in ls)]
['bar', 'foo', 'barbar', 'foobar']
dawg
  • 98,345
  • 23
  • 131
  • 206