36
\$\begingroup\$

I'm trying to port this little F# snippet while staying pythonic:

["something"; "something else"; "blah"; "a string"] |> List.map (fun p -> p, p.Length) |> List.sortBy snd

In case you don't speak F#, it gets the length of each string, then sorts by length.

Output:

[("blah", 4); ("a string", 8); ("something", 9); ("something else", 14)]

In Python, this is the best I could do so far:

sorted([(p, len(p)) for p in ["something", "something else", "blah", "a string"]], key=lambda a:a[1])

While correct, this doesn't look very elegant to me, or maybe it's just my non-pythonic eye.

Is this pythonic code? How would you write it? Maybe an imperative style is more appropriate?

\$\endgroup\$

5 Answers 5

29
\$\begingroup\$
data = ["something", "something else", "blah", "a string"]
result = [(x, len(x)) for x in sorted(data, key = len)]

Basically, its more straightforward to sort first then decorate. Although, I'm not sure why you would need the length of the list in your tuple. If you don't really need it sorting by length can be much shorter.

EDIT:

If all I wanted was to output the data, I'd do it like this:

for string in sorted(data, key = len):
    print string, len(string)

If you really wanted to eliminate the two references to len you could do:

mykey = len
for string in sorted(data, key = mykey):
    print string, mykey(string)

But unless you are reusing the code with different mykey's that doesn't seem worthwhile.

\$\endgroup\$
6
  • \$\begingroup\$ @Mauricio Scheffer, what are you doing with it afterwards? \$\endgroup\$ Commented Feb 26, 2011 at 16:24
  • \$\begingroup\$ @Winston Ewert: just printing it to screen. \$\endgroup\$ Commented Feb 26, 2011 at 16:34
  • \$\begingroup\$ @Mauricio Scheffer, so you are just printing this list out? Why do you need the string lengths in there? \$\endgroup\$ Commented Feb 26, 2011 at 16:37
  • \$\begingroup\$ @Winston Ewert: just need to display that information. \$\endgroup\$ Commented Feb 26, 2011 at 16:39
  • 1
    \$\begingroup\$ Nitpicking: the space between the = when using keyword arguments is not PEP8 compliant. \$\endgroup\$
    – Bakuriu
    Commented Sep 29, 2013 at 19:36
6
\$\begingroup\$

I don't think that your solution looks bad. I would probably use a temporary variable to make the line length a bit more readable. You could consider itemgetter from the operator module.

E.g.

from operator import itemgetter

orig_list = ["something", "something else", "blah", "a string"]
mylist = [(p, len(p)) for p in orig_list]
mylist.sort(itemgetter(1))

Personally I think that this is just as readable.

mylist = sorted([(p, len(p)) for p in orig_list], key=itemgetter(1))
\$\endgroup\$
5
\$\begingroup\$

If you don't want to call len twice for each item (if, for example, you need to call some expensive function instead of len), you can sort by the second item without lambdas by using itemgetter.

from operator import itemgetter    

data = ["something", "something else", "blah", "a string"]
l = [(s, len(s)) for s in data]
l.sort(key = itemgetter(1))

However, if the order of members is not important, it would be better to place the length first in the tuple, because the default behaviour for sorting tuples is to compare the elements in order.

data = ["something", "something else", "blah", "a string"]
l = sorted((len(s), s) for s in data)

You can then switch them around during output if you want:

for length, item in l:
    print item, length
\$\endgroup\$
1
\$\begingroup\$

Here's another option. For the key function, it specifies a lambda that takes a two-item sequence, and unpacks the two items into "s" and "l", and returns "l". This avoids poking around each (string, length) pair by magic number, and also enforces a bit of a type constraint on the items to sort. Also, it breaks lines in convenient places, which is perfectly legal in Python:

sorted([(p, len(p)) for p 
       in ("something", 
           "something else", 
           "blah", 
           "a string")], 
       key=lambda (s, l): l)

And here is a version that uses a generator comprehension instead of a list comprehension. Generator expressions are evaluated as items are pulled from them, rather than all at once. In this example there's no advantage, but when using expressions where items are expensive to create, or where iteration could terminate early (like database queries), generators are a big win:

sorted(((p, len(p)) for p 
       in ("something", 
           "something else", 
           "blah", 
           "a string")), 
       key=lambda (s, l): l)

And this version deterministically handles cases where there is some ambiguity on sorting only by length:

sorted(((p, len(p)) for p 
       in ("something", 
           "something else", 
           "four things", 
           "five things", 
           "a string")), 
       key=lambda (s, l): (l, s))
\$\endgroup\$
1
\$\begingroup\$

If the order of the items in the tuple isn't important (depends on what you're going to do with it), then it's easier to put the length first and the string second. The standard tuple sorting sorts on the first element, or if they're equal on the second, and so on.

sorted(
    (len(p), p) for p in
    ("something", "something else", "four things", "five things", "a string"))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.