agf1997
agf1997

Reputation: 2898

Sorting python list to make letters come before numbers

I'm pretty new to python and I'm looking for a way to sort a list placing words before numbers.

I understand you can use sort to do the following :

a = ['c', 'b', 'd', 'a']
a.sort()
print(a)
['a', 'b', 'c', 'd']

b = [4, 2, 1, 3]
b.sort()
print(b)
[1, 2, 3, 4]

c = ['c', 'b', 'd', 'a', 4, 2, 1, 3]
c.sort()
print(c)
[1, 2, 3, 4, 'a', 'b', 'c', 'd']

However I'd like to sort c to produce :

['a', 'b', 'c', 'd', 1, 2, 3, 4]

Thanks in advance

Upvotes: 9

Views: 18944

Answers (6)

dawg
dawg

Reputation: 103864

If you have a list of mixed ascii and numeric types, you need to determine what is a numeric object type. You can use the Numbers abstract base class to determine what is an instance of a number (int, float, long, complex) class, including all derived classes (bool, Decimal, Franctions, etc):

>>> from numbers import Number
>>> [isinstance(n, Number) for n in (0,1.1,0j,True,'a')]
[True, True, True, True, False]

Once you know what is an instance of a number, you can use a Python bool to create a primary sort key with the list item itself as a secondary key (ie, tuples comprising of [(True, 1.1), (False, 'abc'), etc]) False will sort lower than True just as 0<1 in a typical ascending order sort, so that is just what we wanted.

Applying that to your list (expanded) as an example:

>>> c = ['c', 'b', 'd', 'a', 4, 2, 1, 35, 1.1, 6L, 'aac', True]
>>> sorted(c, key=lambda e: (isinstance(e, Number), e))
['a', 'aac', 'b', 'c', 'd', 1, True, 1.1, 2, 4, 6L, 35]

Note that different numeric types are being correctly sorted (1<=True<1.1<6L<35)

Upvotes: 1

user648852
user648852

Reputation:

The default Python sort is asciibetical

Given:

>>> c = ['c', 'b', 'd', 'a', 'Z', 0, 4, 2, 1, 3]

the default sort is:

>>> sorted(c)
[0, 1, 2, 3, 4, 'Z', 'a', 'b', 'c', 'd']

It will also not work at all on Python3:

Python 3.4.3 (default, Feb 25 2015, 21:28:45) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> c = ['c', 'b', 'd', 'a', 'Z', 0, 4, 2, 1, 3]
>>> sorted(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

The solution is create a tuple with a index integer as the first element (based on the items type) and the items itself as the next element. Python 2 and 3 will sort tuples with the second element heterogeneous types.

Given:

>>> c = ['c', 'b', 'd', 'a', 'Z', 'abc', 0, 4, 2, 1, 3,33, 33.333]

Note mixture of characters, integers, strings, floats

def f(e):
    d={int:1, float:1, str:0}
    return d.get(type(e), 0), e

>>> sorted(c, key=f)   
['Z', 'a', 'abc', 'b', 'c', 'd', 0, 1, 2, 3, 4, 33, 33.333]

Or, if you want a lambda:

>>> sorted(c,key = lambda e: ({int:1, float:1, str:0}.get(type(e), 0), e)))  
['Z', 'a', 'abc', 'b', 'c', 'd', 0, 1, 2, 3, 4, 33, 33.333]

Based on the comment from "the wolf", you can also do:

>>> sorted(c,key = lambda e: (isinstance(e, (float, int)), e))
['Z', 'a', 'abc', 'b', 'c', 'd', 0, 1, 2, 3, 4, 33, 33.333]

Which I must agree is better...

Upvotes: 5

Omid
Omid

Reputation: 2667

Let's say we have a hybrid list like this:

c = ['s', 'a',2 , 'j', 9, 'e', 11, 't', 'k', 12, 'q']

first we need to slice up the list into two separate parts (strings and integers), sort them individually and then append them in the end. Here's one way to do it:

>>> c = sorted([i for i in c if not str(i).isdigit()]) + sorted([i for i in c if str(i).isdigit()])

Now you get:

>>> c
['a', 'e', 'j', 'k', 'q', 's', 't', 2, 9, 11, 12]

Upvotes: 1

Don
Don

Reputation: 17606

You can also use the cmp parm:

c = ['c', 'b', 'd', 'a', 4, 2, 1, 3]

def compare_function(a, b):
    if isinstance(a, str) and isinstance(b, int):
        return -1
    if isinstance(a, int) and isinstance(b, str):
        return 1
    return cmp(a, b)

c.sort(cmp=compare_function)

Upvotes: 0

a p
a p

Reputation: 3208

[sorted([letter for letter in c if isinstance(letter, str)]) + \
 sorted([number for number in c if isinstance(number, int)]]

Should do it.

Upvotes: 1

Kevin
Kevin

Reputation: 76194

You could provide a custom key argument which gives a lower value to strings than it does to ints:

>>> c = ['c', 'b', 'd', 'a', 4, 2, 1, 3]
>>> c.sort(key = lambda item: ([str,int].index(type(item)), item))
>>> c
['a', 'b', 'c', 'd', 1, 2, 3, 4]

Upvotes: 11

Related Questions