4

A previous stackoverflow question explains how to sort a list of strings alpha-numerically. I would like to sort a list of tuples alphanumerically by the tuple's first element.

Example 1:

>>> sort_naturally_tuple([('b', 0), ('0', 1), ('a', 2)])
[('0', 1), ('a', 2), ('b', 0)]

Example 2:

>>> sort_naturally_tuple([('b10', 0), ('0', 1), ('b9', 2)])
[('0', 1), ('b9', 2), ('b10', 0)]

Update: To emphasize the alphanumeric factor, please review example 2.

Community
  • 1
  • 1
paragbaxi
  • 3,965
  • 8
  • 44
  • 58

4 Answers4

5

Using the second answer from the other question, generalized to support any method on item as the basis for getting the key:

import re
from operator import itemgetter

def sorted_nicely(l, key):
    """ Sort the given iterable in the way that humans expect."""
    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda item: [ convert(c) for c in re.split('([0-9]+)', key(item)) ]
    return sorted(l, key = alphanum_key)


print sorted_nicely([('b10', 0), ('0', 1), ('b9', 2)], itemgetter(0))

This is exactly the same as that answer except generalized to use any callable as the operation on item. If you just wanted to do it on a string, you'd use lambda item: item, if you wanted to do it on a list, tuple, dict, or set, you'd use operator.itemgetter(key_or_index_you_want), or if you wanted to do it on a class instance you could use operator.attrgetter('attribute_name_you_want').

It gives

[('0', 1), ('b9', 2), ('b10', 0)]

for your example #2.

agf
  • 171,228
  • 44
  • 289
  • 238
  • sure, just change `key[0]` to `key['thekeyyouwanttosortby']` – agf Jul 27 '11 at 18:26
  • Easy! Hmm, how about a step further? Would it be possible to send the `sorted_nicely()` method what you want to sort by? For example `sorted_nicely(l, 'key[0]')` would sort by first element in `l`. Another example is `sorted_nicely(d, 'key[\'the_key_you_want_to_sort_by\')'` would sort dictionary `d` by element `d['the_key_you_want_to_sort_by']`. – paragbaxi Jul 27 '11 at 18:31
  • This is perfecto. Now I can use this for dictionaries, lists, & tuples! – paragbaxi Jul 27 '11 at 20:48
4

Tuples are by default sorted by their elements, starting at the first. So simply do

L = [('b', 0), ('0', 1), ('a', 2)]
L.sort()
print L
# or create a new, sorted list
print sorted([('b', 0), ('0', 1), ('a', 2)])

The question you liked to talks about natural sorting, which is different from normal (alphanumeric) sorting.

Lets say you want to do natural sort on the first item only:

import re
def naturalize(item):
    # turn 'b10' into ('b',10) which sorts correctly
    m = re.match(r'(\w+?)(\d+)', item)
    return m.groups()
# now sort by using this function on the first element of the tuple:
print sorted(L, key=lambda tup: naturalize(tup[0]))
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • Thanks. My apologies, I did not emphasize the natural sort. `sorted([('b10', 0), ('0', 1), ('b9', 2)])` returns `[('0', 1), ('b10', 0), ('b9', 2)]`, which is incorrect as `('b9', 2)` should precede `('b10', 0)`. – paragbaxi Jul 27 '11 at 18:18
1

As others have pointed out, sorted will use the first element of the tuple by default. If you wish to modify this default behavior you can specify a key to be used during the comparisons.

sorted([('b', 0), ('0', 1), ('a', 2)])

Will return the same as:

sorted([('b', 0), ('0', 1), ('a', 2)], key=lambda item: item[0])

To sort by the second element however try:

sorted([('b', 0), ('0', 1), ('a', 2)], key=lambda item: item[1])
sampwing
  • 1,238
  • 1
  • 10
  • 13
  • 4
    Use `operator.itemgetter` for this method, not a lambda. – agf Jul 27 '11 at 18:14
  • Wasn't aware of this. So something akin to sorted(arr, key=itemgetter(1)(arr)) to sort by the second element? Thanks btw – sampwing Jul 27 '11 at 18:17
  • Thanks. My apologies, I did not emphasize the natural sort. `sorted([('b10', 0), ('0', 1), ('b9', 2)])` returns `[('0', 1), ('b10', 0), ('b9', 2)]`, which is incorrect as `('b9', 2)` should precede `('b10', 0)`. – paragbaxi Jul 27 '11 at 18:18
  • `from operator import itemgetter; sorted([('b', 0), ('0', 1), ('a', 2)], key=itemgetter(1))` – agf Jul 27 '11 at 18:18
  • `sorted([('b10', 0), ('0', 1), ('b9', 2)], key=lambda item: item[0])` returns `[('0', 1), ('b10', 0), ('b9', 2)]`, which is incorrect as `('b9', 2)` should precede `('b10', 0)`. – paragbaxi Jul 27 '11 at 18:24
0

The natsort module does this by default without any extra work

>>> from natsort import natsorted
>>> natsorted([('b', 0), ('0', 1), ('a', 2)])
[('0', 1), ('a', 2), ('b', 0)]
>>> natsorted([('b10', 0), ('0', 1), ('b9', 2)])
[('0', 1), ('b9', 2), ('b10', 0)]
SethMMorton
  • 45,752
  • 12
  • 65
  • 86