79

I have a list containing version strings, such as things:

versions_list = ["1.1.2", "1.0.0", "1.3.3", "1.0.12", "1.0.2"]

I would like to sort it, so the result would be something like this:

versions_list = ["1.0.0", "1.0.2", "1.0.12", "1.1.2", "1.3.3"]

The order of precedence for the digits should obviously be from left to right, and it should be descending. So 1.2.3 comes before 2.2.3 and 2.2.2 comes before 2.2.3.

How do I do this in Python?

tripleee
  • 139,311
  • 24
  • 207
  • 268
Zack
  • 1,741
  • 2
  • 15
  • 19

4 Answers4

145

You can also use distutils.version module of standard library:

from distutils.version import StrictVersion
versions = ["1.1.2", "1.0.0", "1.3.3", "1.0.12", "1.0.2"]
versions.sort(key=StrictVersion)

Gives you:

['1.0.0', '1.0.2', '1.0.12', '1.1.2', '1.3.3']

It can also handle versions with pre-release tags, for example:

versions = ["1.1", "1.1b1", "1.1a1"]
versions.sort(key=StrictVersion)

Gives you:

["1.1a1", "1.1b1", "1.1"]

Documentation: https://github.com/python/cpython/blob/3.2/Lib/distutils/version.py#L101

andreypopp
  • 6,427
  • 5
  • 23
  • 25
  • 1
    Seems more pythonic then Eli's solution. – Vojta Rylko Apr 04 '10 at 11:41
  • 24
    There's also distutils.version.LooseVersion which is a little more forgiving with version numbers that end in letters ['1.0b', '1.0.2-final'], etc. or whatnot - I prefer this version since StrictVersion seems to be more oriented towards Python distutils specific version strings, LooseVersion caters to a wider swath of potential version strings you'll see in the wild. – synthesizerpatel Jul 24 '11 at 06:32
  • `StrictVersion` does not handle versions such as '9.20.00.0': invalid version number is returned. I'm wondering if, however, this is because the actual version is indicated as `u'9.20.00.0'` ...??? Maybe this needs to be decoded to UTF-8. – IAbstract May 07 '15 at 20:22
  • 3
    In case you need more freedom, you could use distutils.version.LooseVersion over StrictVersion. See http://epydoc.sourceforge.net/stdlib/distutils.version.LooseVersion-class.html – ferdy Feb 18 '16 at 09:21
  • Unfortunately, even `LooseVersion` doesn't work if you have `['0.1.0', 'latest']` like you might get back from a docker registry. For that, I use the [natsort](https://stackoverflow.com/a/31425911/117471) answer on this page. – Bruno Bronosky Dec 19 '17 at 00:17
  • this is a great solution, along with the natsort method – Raveen Beemsingh Mar 18 '19 at 13:08
  • what is the diff between using `key=StrictVersion` to `key=LooseVersion`? – A. Man Dec 28 '20 at 09:20
  • Note that `distutils` is getting deprecated, see https://www.python.org/dev/peps/pep-0632/. It mentions using `packaging` instead (which is third-party). – renstrm Apr 26 '21 at 13:16
98

Split each version string to compare it as a list of integers:

versions_list.sort(key=lambda s: map(int, s.split('.')))

Gives, for your list:

['1.0.0', '1.0.2', '1.0.12', '1.1.2', '1.3.3']

In Python3 map no longer returns a list, So we need to wrap it in a list call.

versions_list.sort(key=lambda s: list(map(int, s.split('.'))))

The alternative to map here is a list comprehension. See this post for more on list comprehensions.

versions_list.sort(key=lambda s: [int(u) for u in s.split('.')])
Community
  • 1
  • 1
Eli Bendersky
  • 231,995
  • 78
  • 333
  • 394
  • For the regular expression solution you would just replace the s with the expression that returns the group that you want. For example: lambda s: map(int, re.search(myre, s).groups[0].split('.')) – Andrew Cox Apr 04 '10 at 10:09
  • 4
    This is pure elegance. – Balthazar Rouberol Sep 16 '13 at 20:20
  • Sort return None, but list is sorted. – themadmax Apr 12 '16 at 08:11
  • 3
    That key function will not work in Python 3 because `map` returns an iterator in Python 3, not a list. But this will work in both versions: `key=lambda s: [int(u) for u in s.split('.')])`. – PM 2Ring Jun 02 '16 at 21:04
  • 1
    Recommend you assume the reader is using Python 3 and show python 2 as the exception, if that's even needed. – macetw Apr 19 '18 at 19:04
  • How does the `s:` part of `.sort()` work? (I'm confused about where the `s` comes from) – ioannes Aug 26 '20 at 17:53
19

natsort proposes "natural sorting"; wich works very intuitively (in Python 3)

from natsort import natsorted
versions = ["1.1.2", "1.0.0", "1.3.3", "1.0.12", "1.0.2"]
natsorted(versions)

gives

['1.0.0', '1.0.2', '1.0.12', '1.1.2', '1.3.3']

but it works as well on complete package names with version number:

versions = ['version-1.9', 'version-2.0', 'version-1.11', 'version-1.10']
natsorted(versions)

gives

['version-1.9', 'version-1.10', 'version-1.11', 'version-2.0']
Chris Maes
  • 26,426
  • 4
  • 84
  • 113
  • 1
    how to implement this with list of dictionary ? i.e. [{'env': 'REE', 'version': 'API-1.1.12'}, {'env': 'REE', 'version': 'API-1.2.0'}] I want to implement sorting based on version key. – Vijaysinh Parmar Sep 25 '19 at 05:27
2

I also solved this question using Python, although my version does some extra things, here is my code:

def answer(l):
    list1 = [] # this is the list for the nested strings
    for x in l:
        list1.append(x.split("."))
    list2 = [] # this is the same list as list one except everything  is an integer in order for proper sorting
    for y in list1:
        y = list(map(int, y))
        list2.append(y)
    list3 = sorted(list2) #this is the sorted list of of list 2
    FinalList = [] # this is the list that converts everything back to the way it was
    for a in list3:
        a = '.'.join(str(z) for z in a)
        FinalList.append(a)
    return FinalList

For versions there exist three things; Major, Minor, and the revision. What this does is that it organises it so that '1' will come before '1.0' which will come before '1.0.0'. Also, another plus, no need to import any libraries incase you don't have them, and it works with old versions of Python, this one was specifically meant for Version 2.7.6. Anyway, here are a few examples:

Inputs:
    (string list) l = ["1.1.2", "1.0", "1.3.3", "1.0.12", "1.0.2"]
Output:
    (string list) ["1.0", "1.0.2", "1.0.12", "1.1.2", "1.3.3"]

Inputs:
    (string list) l = ["1.11", "2.0.0", "1.2", "2", "0.1", "1.2.1", "1.1.1", "2.0"]
Output:
    (string list) ["0.1", "1.1.1", "1.2", "1.2.1", "1.11", "2", "2.0", "2.0.0"]

If you have any questions, just comment on the answer!

Kye Russell
  • 3,605
  • 3
  • 17
  • 42