219

I know that this sounds trivial, but I did not realize that the sort() function of Python was weird. I have a list of "numbers" that are actually in string form, so I first convert them to ints, then attempt a sort.

list1=["1","10","3","22","23","4","2","200"]
for item in list1:
    item=int(item)

list1.sort()
print list1

Gives me:

['1', '10', '2', '200', '22', '23', '3', '4']

I want

['1','2','3','4','10','22','23','200']

I've looked around for some of the algorithms associated with sorting numeric sets, but the ones I found all involved sorting alphanumeric sets.

I know this is probably a no-brainer problem, but Google and my textbook don't offer anything more or less useful than the .sort() function.

5
  • 16
    Note that your for loop does not do what I suspect that you think it does. Commented Aug 6, 2010 at 17:21
  • 1
    At no time did you update list1. What made you think list was being updated? Commented Aug 6, 2010 at 17:23
  • The similar problem raise when list1 = ['1', '1.10', '1.11', '1.1', '1.2'] is provided as input. Instead of getting output as ['1', '1.1', '1.2', '1.10', '1.11'], I am getting ['1', '1.1', '1.10', '1.11', '1.2'] Commented May 20, 2016 at 3:46
  • 2
    in python 3 you may want to use sorted(mylist) Commented Dec 23, 2018 at 15:02
  • Related: Sort a list of numerical strings in ascending order Commented Jul 31, 2023 at 20:14

16 Answers 16

240

You haven't actually converted your strings to ints. Or rather, you did, but then you didn't do anything with the results. What you want is:

list1 = ["1","10","3","22","23","4","2","200"]
list1 = [int(x) for x in list1]
list1.sort()

If for some reason you need to keep strings instead of ints (usually a bad idea, but maybe you need to preserve leading zeros or something), you can use a key function. sort takes a named parameter, key, which is a function that is called on each element before it is compared. The key function's return values are compared instead of comparing the list elements directly:

list1 = ["1","10","3","22","23","4","2","200"]
# call int(x) on each element before comparing it
list1.sort(key=int)
# or if you want to do it all in the same line
list1 = sorted([int(x) for x in list1]) 
Sign up to request clarification or add additional context in comments.

5 Comments

when I try key=int in 2.7 I get None
This works if the list element is stored as "integer", how shall be handled in case of float values? Eg., list1 = [1, 1.10, 1.11, 1.1, 1.2]
@KI4JGT the sort method modifies the list and returns None. So instead of list1 = list1.sort(key=int), use just list1.sort(key=int) and list1 will already be sorted.
@KI4JGT .sort() is an in place operator, it returns None, it sorts the list, you may want to use sorted()
Even if it solved the original problem, I used the natsorted method (stackoverflow.com/a/50494717/11159476) since I had to sort list of images got with glob.glob("*.jpg) returning actual strings, for example ["1.jpg", "10.jpg", ... "19.jpg", "2.jpg", "20.jpg", ..., "29.jpg", "3.jpg", ..., "9.jpg", ...] and it should work for you too @sathish, even if you should already have found your solution ^^
101

I approached the same problem yesterday and found a module called natsort, which solves your problem. Use:

from natsort import natsorted # pip install natsort

# Example list of strings
a = ['1', '10', '2', '3', '11']

[In]  sorted(a)
[Out] ['1', '10', '11', '2', '3']

[In]  natsorted(a)
[Out] ['1', '2', '3', '10', '11']

# Your array may contain strings
[In]  natsorted(['string11', 'string3', 'string1', 'string10', 'string100'])
[Out] ['string1', 'string3', 'string10', 'string11', 'string100']

It also works for dictionaries as an equivalent of sorted.

Comments

49

You could pass a function to the key parameter to the .sort method. With this, the system will sort by int(x) instead of x.

list1.sort(key=int)

BTW, to convert the list to integers permanently, use the map function

list1 = list(map(int, list1))   # you don't need to call list() in Python 2.x

or list comprehension

list1 = [int(x) for x in list1]

1 Comment

list1.sort(key=int) works in place and also doesn't change the list content, great !
33

In case you want to use sorted() function: sorted(list1, key=int)

It returns a new sorted list.

1 Comment

Works with sets too!
23

You can also use:

import re

def sort_human(l):
    convert = lambda text: float(text) if text.isdigit() else text
    alphanum = lambda key: [convert(c) for c in re.split('([-+]?[0-9]*\.?[0-9]*)', key)]
    l.sort(key=alphanum)
    return l

This is very similar to other stuff that you can find on the internet but also works for alphanumericals like [abc0.1, abc0.2, ...].

3 Comments

You should probably either return a new list or modify the list, not both. The above code modifies the list and then returns it. Use sorted() to make a new list instead.
Unfortunately this only works when letters and numbers don't appear in the same order; e.g. ["abc123", "123abc"]: TypeError: '<' not supported between instances of 'float' and 'str'. Solution: replace the covert function with (float(text), "") if text.isdigit() else (float("inf"), text). It will always return a (float, str) tuple, so comparison will always work.
On a list of my own, for some reason, the piece of code didn't worked, but that one did: alphanum = lambda key: [float(c) for c in re.findall('([+-]?\d+\.?\d*)', key)]
14

Python's sort isn't weird. It's just that this code:

for item in list1:
   item=int(item)

isn't doing what you think it is - item is not replaced back into the list, it is simply thrown away.

Anyway, the correct solution is to use key=int as others have shown you.

Comments

9

Seamus Campbell's answer doesn't work on Python 2.x.

list1 = sorted(list1, key=lambda e: int(e)) using lambda function works well.

Comments

3

Try this. It’ll sort the list in-place in descending order (there isn’t any need to specify a key in this case):

Process

listB = [24, 13, -15, -36, 8, 22, 48, 25, 46, -9]
listC = sorted(listB, reverse=True) # listB remains untouched
print listC

output:

 [48, 46, 25, 24, 22, 13, 8, -9, -15, -36]

Comments

2

The real problem is that 'sort' sorts things alphanumerically.

So if you have a list, ['1', '2', '10', '19'], and run 'sort', you get ['1', '10'. '19', '2']. That is, 10 comes before 2, because it looks at the first character and sorts starting from that.

It seems most methods in Python return things in that order. For example, if you have a directory named 'abc' with the files labelled as 1.jpg, 2.jpg, etc., say, up to 15.jpg and you do file_list=os.listdir(abc), the file_list is not ordered as you expect, but rather as file_list=['1.jpg', '11.jpg'---'15.jpg', '2.jpg].

If the order in which files are processed is important (presumably that's why you named them numerically) the order is not what you think it will be. You can avoid this by using "zeros" padding. For example if you have a list, alist=['01', '03', '05', '10', '02','04', '06] and you run 'sort' on it, you get the order you wanted, alist=['01', '02', etc.], because the first character is 0 which comes before 1. The amount of zeros padding you need is determined by the largest value in the list.

For example, if the largest is, say, between 100 and 1000, you need to pad single digits as 001, 002 ---010, 011--100, 101, etc.

Comments

2

I've arrived here looking for a generic string, containing numbers at any position, which I think is not really solved in current answers (or, if covered, is way simpler).

I've come up with a simple solution covering that scenario:

def numeric_sort(input: Iterable[str]) -> List[str]:
    def repl(num):
        return f"{int(num[0]):010d}"

    return sorted(input, key=lambda i: re.sub(r'(\d+)', repl, i))


def test_numeric_sort():
    assert numeric_sort(["a2", "a11",  "a1"])   == ["a1", "a2", "a11"]
    assert numeric_sort(["a2", "a11",  "b1"])   == ["a2", "a11", "b1"]
    assert numeric_sort(["a2", "a2b3", "a2b1"]) == ["a2", "a2b1", "a2b3"]

You need to choose a limit for the maximum number (shouldn't be a problem). You can change int...:d for float...:f, once you adjust the regex.

Comments

1

It may be not the best Python code, but for string lists like ['1', '1.0', '2.0', '2', '1.1', '1.10', '1.11', '1.2', '7', '3', '5'], with the expected target ['1', '1.0', '1.1', '1.2', '1.10', '1.11', '2', '2.0', '3', '5', '7'] helped me...

unsortedList = ['1', '1.0', '2.0', '2', '1.1', '1.10', '1.11', '1.2', '7', '3', '5']
sortedList = []
sortDict = {}
sortVal = []
# Set zero correct (integer): example: 1.000 will be 1 and breaks the order
zero = "000"
for i in sorted(unsortedList):
  x = i.split(".")
  if x[0] in sortDict:
    if len(x) > 1:
        sortVal.append(x[1])
    else:
        sortVal.append(zero)
    sortDict[x[0]] = sorted(sortVal, key = int)
  else:
    sortVal = []
    if len(x) > 1:
        sortVal.append(x[1])
    else:
        sortVal.append(zero)
    sortDict[x[0]] = sortVal
for key in sortDict:
  for val in sortDict[key]:
    if val == zero:
       sortedList.append(str(key))
    else:
       sortedList.append(str(key) + "." + str(val))
print(sortedList)

1 Comment

Welcome to SO! When you are about to answer an old question (this one is over 10 years old) that already has an accepted answer (that is the case here) please ask yourself: Do I really have a substantial improvement to offer? If not, consider refraining from answering.
0

The most recent solution is right. You are reading solutions as a string, in which case the order is 1, then 100, then 104 followed by 2 then 21, then 2001001010, 3 and so forth.

You have to cast your input as an int instead:

sorted strings:

stringList = (1, 10, 2, 21, 3)

sorted ints:

intList = (1, 2, 3, 10, 21)

To cast, just put the stringList inside int (blahblah).

Again:

stringList = (1, 10, 2, 21, 3)

newList = int (stringList)

print newList

=> returns (1, 2, 3, 10, 21)

6 Comments

TypeError: int() argument must be a string or a number, not 'tuple'
Also, the strings in your stringList should have quotes.
That's a helluva prediction to make: "the most recent solution is right" ;)
Re "The most recent solution is right": Such relative references are not reliable (or stable). Which answer does it refer to?
OK, the OP has left the building: "Last seen more than 9 years ago"
|
0

A simple way to sort a numerical list:

numlists = ["5","50","7","51","87","97","53"]
results = list(map(int, numlists))
results.sort(reverse=False)
print(results)

Comments

0

If you want to use strings of the numbers, better take another list as shown in my code. It will work fine.

list1 = ["1", "10", "3", "22", "23", "4", "2", "200"]

k = []
for item in list1:
    k.append(int(item))

k.sort()
print(k)
# [1, 2, 3, 4, 10, 22, 23, 200]

Comments

0

Converting to int is nice but you can't always convert the whole string to int. So I just convert the \d+ substrings to int.

import re
from typing import List

def make_string_sortable_numerically(string:str) -> List[str|int]:
    def isolate_digits(x:str) -> List[str]:
        return re.split(r'(\d+)', x)
    def convert_digits_to_int(substrings:List[str]) -> List[str|int]:
        return [int(x) if str.isdigit(x) else x for x in substrings]
    return convert_digits_to_int(isolate_digits(string))

unsorted = ["abc100", "abc11"]
print(f"sored normally: {sorted(unsorted)}")
print(f"sorted numerically: {sorted(unsorted, key=make_string_sortable_numerically)}")
sored normally: ['abc100', 'abc11']
sorted numerically: ['abc11', 'abc100']

actually, the above answer has the same problem as https://stackoverflow.com/a/40039556/6307935. Here is something that I think should be robust:

def _make_string_sortable_numerically(string:str) -> List[Tuple[int, int]]:
    """
    each character becomes a tuple of two ints. The first int is either 0,1, or 2
    0 for characters that come before numbers, 1 for numbers, 2 for after numbers
    the second int is the unicode value of the character, or the integer value of the number
    that this character is a part of.
                $         7         8         9         a        ~
    "$789a~" -> [[0, 36], [1, 789], [1, 789], [1, 789], [2, 97], [2, 126]]
    """
    output = [[None, None] for _ in range(len(string))]
    skip_these_indexes = [False]*len(string)
    for i, char in enumerate(string):
        if skip_these_indexes[i]:
            continue
        char_int = ord(char)
        if char_int < ord("0"):
            output[i] = (0, char_int)
        elif str.isdigit(char):
            first_digit_index = i
            last_digit_index = i
            while (last_digit_index < len(string)-1 and str.isdigit(string[last_digit_index+1])):
                last_digit_index += 1
            this_number = int(string[first_digit_index:last_digit_index+1])
            for digit_index in range(first_digit_index, last_digit_index+1):
                skip_these_indexes[digit_index] = True
                output[digit_index] = (1, this_number)
        elif char_int > ord("9"):
            output[i] = (2, char_int)
    return output

Comments

-6

Use:

scores = ['91','89','87','86','85']
scores.sort()
print (scores)

This worked for me using Python version 3, though it didn't in version 2.

1 Comment

Try sorting with '11 and '100' there, that's when things get interesting.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.