0

I'm trying to sort a list with numbers and letter by numeric value.

list = ['3a', '13a', '5a', '11a']

When I use the .sort function it sorts it like this:

['11a', '13a', '3a', '5a']

Like it's only looking at the first number.

cs95
  • 379,657
  • 97
  • 704
  • 746
Jackson
  • 483
  • 5
  • 20

4 Answers4

4

If you just use an unmodified sorted it will sort on the ASCII codes on each character in the string:

>>> sorted(['2','11','01000'])
['01000', '11', '2']

vs adding a key value that tells sorted to use the integer value:

>>> sorted(['2','11','01000'], key=int)
['2', '11', '01000']

For strings that are a mixture of integers and letters, use a natural sort where letters are sorted lexicographically and numbers are treated to simple integer comparison:

import re

def natural_sort(l): 
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

>>> li = ['3a', '13a', '5a', '11a']
>>> natural_sort(li)
['3a', '5a', '11a', '13a']

The advantage of this version is it handles multiple groups of numbers and letters:

>>> li = ['3a33', '3a2', '3b1', '3a1', '5a', '11a']
>>> natural_sort(li)
['3a1', '3a2', '3a33', '3b1', '5a', '11a']

As well as what you would expect with the letters in front:

>>> li = ['a33', 'a2', 'b1', 'a1', 'a', 'a00004']
>>> natural_sort(li)
['a', 'a1', 'a2', 'a00004', 'a33', 'b1']

(PS: Best to not call a python list list because it stomps on the name of the function...)

dawg
  • 98,345
  • 23
  • 131
  • 206
2

You can pass in a lambda for a sorting key.

list = ['3a', '13a', '5a', '11a']
list.sort(key=lambda x: int(x[:-1]))

The important part is:

lambda x: int(x[:-1])

which takes in a string, removes the last character, and converts it to an integer.

Try it online!

Oliver Ni
  • 2,606
  • 7
  • 29
  • 44
  • Fixed the try it online link. – Oliver Ni Aug 13 '17 at 02:39
  • Your proposal is unsafe. There may be more that one letter - or no letter at all. – DYZ Aug 13 '17 at 02:47
  • @DYZ The OP specified that the list contained things like this, so I did it. Of course, if it might not, it is best to do some checks beforehand. – Oliver Ni Aug 13 '17 at 02:48
  • "Like" does not mean "exactly." – DYZ Aug 13 '17 at 02:49
  • @JoshForcier Of all the answers you could have accepted, I would recommend this the least. It works specifically for your single case and nothing more. – cs95 Aug 13 '17 at 09:27
  • Yeah I understand that. It looks like using natural sort would be the best, but for my purposes I can't use import. – Jackson Aug 13 '17 at 14:18
2

Your list is a list of strings. So list.sort will sort the elements lexicographically. You can change this by passing a key to your sorting function. You can use re.match (import re first) to extract the digits and compare. Something like this:

In [30]: list_ = ['3a', '13a', '5a', '11a']

In [31]: matcher = re.compile('\d+')

In [32]: sorted(list_, key=lambda x: int(matcher.match(x).group()))
Out[32]: ['3a', '5a', '11a', '13a']

This works best if your elements have something more than a at the end of them.

cs95
  • 379,657
  • 97
  • 704
  • 746
1

There is package called natsort

from natsort import natsorted
LL = ['3a', '13a', '5a', '11a']
natsorted(LL)

Out[296]: ['3a', '5a', '11a', '13a']

@dawg's example using natsort:

li = ['3a33', '3a2', '3a1', '13a', '5a', '11a']

natsorted(li)
Out[298]: ['3a1', '3a2', '3a33', '5a', '11a', '13a']
BENY
  • 317,841
  • 20
  • 164
  • 234