Comparaison des numéros de version en Python

98

Je veux écrire une cmpfonction -comme qui compare deux numéros de version et les retours -1, 0ou en 1fonction de leur valuses par rapport.

  • Renvoie -1si la version A est antérieure à la version B
  • Renvoie 0si les versions A et B sont équivalentes
  • Renvoie 1si la version A est plus récente que la version B

Chaque sous-section est censée être interprétée comme un nombre, donc 1,10> 1,1.

Les sorties de fonction souhaitées sont

mycmp('1.0', '1') == 0
mycmp('1.0.0', '1') == 0
mycmp('1', '1.0.0.1') == -1
mycmp('12.10', '11.0.0.0.0') == 1
...

Et voici ma mise en œuvre, ouverte à l'amélioration:

def mycmp(version1, version2):
    parts1 = [int(x) for x in version1.split('.')]
    parts2 = [int(x) for x in version2.split('.')]

    # fill up the shorter version with zeros ...
    lendiff = len(parts1) - len(parts2)
    if lendiff > 0:
        parts2.extend([0] * lendiff)
    elif lendiff < 0:
        parts1.extend([0] * (-lendiff))

    for i, p in enumerate(parts1):
        ret = cmp(p, parts2[i])
        if ret: return ret
    return 0

J'utilise Python 2.4.5 btw. (installé sur mon lieu de travail ...).

Voici une petite `` suite de tests '' que vous pouvez utiliser

assert mycmp('1', '2') == -1
assert mycmp('2', '1') == 1
assert mycmp('1', '1') == 0
assert mycmp('1.0', '1') == 0
assert mycmp('1', '1.000') == 0
assert mycmp('12.01', '12.1') == 0
assert mycmp('13.0.1', '13.00.02') == -1
assert mycmp('1.1.1.1', '1.1.1.1') == 0
assert mycmp('1.1.1.2', '1.1.1.1') == 1
assert mycmp('1.1.3', '1.1.3.000') == 0
assert mycmp('3.1.1.0', '3.1.2.10') == -1
assert mycmp('1.1', '1.10') == -1
Johannes Charra
la source
Pas une réponse mais une suggestion - cela pourrait valoir la peine d'implémenter l'algorithme de Debian pour la comparaison des numéros de version (en gros, tri alterné des parties non numériques et numériques). L'algorithme est décrit ici (commençant par "Les chaînes sont comparées de gauche à droite").
hobbs
Blargh. Le sous-ensemble de démarques pris en charge dans les commentaires ne manque jamais de me dérouter. Le lien fonctionne de toute façon, même s'il a l'air stupide.
hobbs
Au cas où les futurs lecteurs en auraient besoin pour l'analyse de la version de l'agent utilisateur, je recommande une bibliothèque dédiée car la variation historique est trop large.
James Broadhead
2
Copie possible des chaînes
John Y
1
Même si la question ici est plus ancienne, il semble que cette autre question ait été ointe en tant que question canonique, autant de questions sont fermées comme des doubles de celle-ci.
John Y

Réponses:

36

Supprimez la partie inintéressante de la chaîne (zéros et points de fin), puis comparez les listes de nombres.

import re

def mycmp(version1, version2):
    def normalize(v):
        return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
    return cmp(normalize(version1), normalize(version2))

C'est la même approche que Pär Wieslander, mais un peu plus compacte:

Voici quelques tests, grâce à " Comment comparer deux chaînes au format de version séparée par des points dans Bash? ":

assert mycmp("1", "1") == 0
assert mycmp("2.1", "2.2") < 0
assert mycmp("3.0.4.10", "3.0.4.2") > 0
assert mycmp("4.08", "4.08.01") < 0
assert mycmp("3.2.1.9.8144", "3.2") > 0
assert mycmp("3.2", "3.2.1.9.8144") < 0
assert mycmp("1.2", "2.1") < 0
assert mycmp("2.1", "1.2") > 0
assert mycmp("5.6.7", "5.6.7") == 0
assert mycmp("1.01.1", "1.1.1") == 0
assert mycmp("1.1.1", "1.01.1") == 0
assert mycmp("1", "1.0") == 0
assert mycmp("1.0", "1") == 0
assert mycmp("1.0", "1.0.1") < 0
assert mycmp("1.0.1", "1.0") > 0
assert mycmp("1.0.2.0", "1.0.2") == 0
gnud
la source
2
J'ai peur que cela ne fonctionne pas, le rstrip(".0")changera ".10" en ".1" dans "1.0.10".
RedGlyph
Désolé, mais avec votre fonction: mycmp ('1.1', '1.10') == 0
Johannes Charra
Avec l'utilisation de regex, le problème mentionné ci-dessus est résolu.
gnud
Maintenant que vous avez fusionné toutes les bonnes idées des autres dans votre solution ... :-P encore, c'est à peu près ce que je ferais après tout. J'accepte cette réponse. Merci à tous
Johannes Charra
2
Remarque cmp () a été supprimé dans Python 3: docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
Dominic Cleal
279

Que diriez-vous d'utiliser Python distutils.version.StrictVersion?

>>> from distutils.version import StrictVersion
>>> StrictVersion('10.4.10') > StrictVersion('10.4.9')
True

Donc pour votre cmpfonction:

>>> cmp = lambda x, y: StrictVersion(x).__cmp__(y)
>>> cmp("10.4.10", "10.4.11")
-1

Si vous souhaitez comparer des numéros de version plus complexes, ce distutils.version.LooseVersionsera plus utile, mais assurez-vous de ne comparer que les mêmes types.

>>> from distutils.version import LooseVersion, StrictVersion
>>> LooseVersion('1.4c3') > LooseVersion('1.3')
True
>>> LooseVersion('1.4c3') > StrictVersion('1.3')  # different types
False

LooseVersion n'est pas l'outil le plus intelligent et peut facilement être trompé:

>>> LooseVersion('1.4') > LooseVersion('1.4-rc1')
False

Pour réussir avec cette race, vous devrez sortir de la bibliothèque standard et utiliser l' utilitaire d'analyse de setuptoolsparse_version .

>>> from pkg_resources import parse_version
>>> parse_version('1.4') > parse_version('1.4-rc2')
True

Ainsi, en fonction de votre cas d'utilisation spécifique, vous devrez décider si les distutilsoutils intégrés sont suffisants ou s'il est justifié de les ajouter en tant que dépendance setuptools.

bradley.ayers
la source
2
semble avoir le plus de sens d'utiliser simplement ce qui est déjà là :)
Patrick Wolf
2
Agréable! Avez-vous compris cela en lisant la source? Je ne trouve nulle part de documentation pour distutils.version: - /
Adam Spiers
3
Chaque fois que vous ne trouvez pas de documentation, essayez d'importer le package et utilisez help ().
rspeed
13
Sachez cependant que cela fonctionne StrictVersion UNIQUEMENT avec une version à trois chiffres maximum. Cela échoue pour des choses comme 0.4.3.6!
abergmeier
6
Chaque instance de distributedans cette réponse doit être remplacée par setuptools, qui est livré avec le pkg_resourcespaquet et a depuis ... comme, jamais . De même, il s'agit de la documentation officielle de la pkg_resources.parse_version()fonction fournie avec setuptools.
Cecil Curry
30

La réutilisation est-elle considérée comme de l'élégance dans ce cas? :)

# pkg_resources is in setuptools
# See http://peak.telecommunity.com/DevCenter/PkgResources#parsing-utilities
def mycmp(a, b):
    from pkg_resources import parse_version as V
    return cmp(V(a),V(b))
conny
la source
7
Hmm, ce n'est pas si élégant lorsque vous faites référence à quelque chose en dehors de la bibliothèque standard sans expliquer où l'obtenir. J'ai soumis une modification pour inclure l'URL. Personnellement, je préfère utiliser distutils - cela ne semble pas valoir la peine de tirer un logiciel tiers pour une tâche aussi simple.
Adam Spiers
1
@ Adam- Spires wut? Avez-vous même lu le commentaire? pkg_resourcesest un setuptoolspackage groupé. Depuis setuptoolsest effectivement obligatoire sur toutes les installations Python, pkg_resourcesest effectivement disponible partout. Cela dit, le sous- distutils.versionpaquet est également utile - bien que considérablement moins intelligent que la pkg_resources.parse_version()fonction de niveau supérieur. Ce que vous devriez exploiter dépend du degré de folie que vous attendez dans les chaînes de version.
Cecil Curry
@CecilCurry Oui, bien sûr, j'ai lu le commentaire (ary), c'est pourquoi je l'ai édité pour l'améliorer, puis j'ai déclaré que j'avais. Vous n'êtes probablement pas en désaccord avec ma déclaration qui setuptoolsest en dehors de la bibliothèque standard, mais plutôt avec ma préférence déclarée pour distutils ce cas . Alors qu'entendez-vous exactement par "effectivement obligatoire", et pouvez-vous fournir la preuve que c'était "effectivement obligatoire" il y a 4,5 ans lorsque j'ai rédigé ce commentaire?
Adam Spiers
12

Pas besoin d'itérer sur les tuples de version. L'opérateur de comparaison intégré sur les listes et les tuples fonctionne déjà exactement comme vous le souhaitez. Vous aurez juste besoin d'étendre les listes de versions à la longueur correspondante. Avec python 2.6, vous pouvez utiliser izip_longest pour remplir les séquences.

from itertools import izip_longest
def version_cmp(v1, v2):
    parts1, parts2 = [map(int, v.split('.')) for v in [v1, v2]]
    parts1, parts2 = zip(*izip_longest(parts1, parts2, fillvalue=0))
    return cmp(parts1, parts2)

Avec les versions inférieures, un piratage de carte est requis.

def version_cmp(v1, v2):
    parts1, parts2 = [map(int, v.split('.')) for v in [v1, v2]]
    parts1, parts2 = zip(*map(lambda p1,p2: (p1 or 0, p2 or 0), parts1, parts2))
    return cmp(parts1, parts2)
Fourmis Aasma
la source
Cool, mais difficile à comprendre pour quelqu'un qui ne peut pas lire le code comme la prose. :) Eh bien, je suppose que vous ne pouvez raccourcir la solution qu'au détriment de la lisibilité ...
Johannes Charra
10

C'est un peu plus compact que votre suggestion. Plutôt que de remplir la version plus courte avec des zéros, je supprime les zéros de fin des listes de versions après le fractionnement.

def normalize_version(v):
    parts = [int(x) for x in v.split(".")]
    while parts[-1] == 0:
        parts.pop()
    return parts

def mycmp(v1, v2):
    return cmp(normalize_version(v1), normalize_version(v2))
Pär Wieslander
la source
Belle, merci. Mais j'espère toujours une ou deux lignes ...;)
Johannes Charra
4
+1 @jellybean: les two-liners ne sont pas toujours les meilleurs pour la maintenance et la lisibilité, celui-ci est un code très clair et compact en même temps, en plus, vous pouvez le réutiliser mycmpà d'autres fins dans votre code si vous en avez besoin.
RedGlyph
@RedGlyph: Vous avez raison. J'aurais dû dire "un deux lignes lisible". :)
Johannes Charra
salut @ Pär Wieslander, quand j'utilise cette solution pour résoudre le même problème au problème Leetcode, j'obtiens une erreur à la boucle while en disant "index de liste hors de portée". Pouvez-vous s'il vous plaît expliquer pourquoi cela se produit? Voici le problème: leetcode.com/explore/interview/card/amazon/76/array-and-strings/…
YouHaveaBigEgo
7

Supprimez la fin .0et .00avec regex, splitet utilisez la cmpfonction qui compare correctement les tableaux:

def mycmp(v1,v2):
 c1=map(int,re.sub('(\.0+)+\Z','',v1).split('.'))
 c2=map(int,re.sub('(\.0+)+\Z','',v2).split('.'))
 return cmp(c1,c2)

Et, bien sûr, vous pouvez le convertir en une seule ligne si les longues files d'attente ne vous dérangent pas.

yu_sha
la source
2
def compare_version(v1, v2):
    return cmp(*tuple(zip(*map(lambda x, y: (x or 0, y or 0), 
           [int(x) for x in v1.split('.')], [int(y) for y in v2.split('.')]))))

C'est une doublure (séparée pour la lisibilité). Pas sûr de la lisibilité ...

mavnn
la source
1
Oui! Et rétréci encore plus ( tuplen'est pas nécessaire btw):cmp(*zip(*map(lambda x,y:(x or 0,y or 0), map(int,v1.split('.')), map(int,v2.split('.')) )))
Paul
2
from distutils.version import StrictVersion
def version_compare(v1, v2, op=None):
    _map = {
        '<': [-1],
        'lt': [-1],
        '<=': [-1, 0],
        'le': [-1, 0],
        '>': [1],
        'gt': [1],
        '>=': [1, 0],
        'ge': [1, 0],
        '==': [0],
        'eq': [0],
        '!=': [-1, 1],
        'ne': [-1, 1],
        '<>': [-1, 1]
    }
    v1 = StrictVersion(v1)
    v2 = StrictVersion(v2)
    result = cmp(v1, v2)
    if op:
        assert op in _map.keys()
        return result in _map[op]
    return result

Implémenter pour php version_compare, sauf "=". Parce que c'est ambigu.

Ryan Fau
la source
2

Les listes sont comparables en Python, donc si quelqu'un convertit les chaînes représentant les nombres en entiers, la comparaison de base Python peut être utilisée avec succès.

J'avais besoin d'étendre un peu cette approche car j'utilise Python3x où la cmpfonction n'existe plus. J'ai dû émuler cmp(a,b)avec (a > b) - (a < b). Et les numéros de version ne sont pas du tout propres et peuvent contenir toutes sortes d'autres caractères alphanumériques. Il y a des cas où la fonction ne peut pas dire l'ordre alors elle retourne False(voir le premier exemple).

Donc, je poste ceci même si la question est ancienne et répond déjà, car cela peut gagner quelques minutes dans la vie de quelqu'un.

import re

def _preprocess(v, separator, ignorecase):
    if ignorecase: v = v.lower()
    return [int(x) if x.isdigit() else [int(y) if y.isdigit() else y for y in re.findall("\d+|[a-zA-Z]+", x)] for x in v.split(separator)]

def compare(a, b, separator = '.', ignorecase = True):
    a = _preprocess(a, separator, ignorecase)
    b = _preprocess(b, separator, ignorecase)
    try:
        return (a > b) - (a < b)
    except:
        return False

print(compare('1.0', 'beta13'))    
print(compare('1.1.2', '1.1.2'))
print(compare('1.2.2', '1.1.2'))
print(compare('1.1.beta1', '1.1.beta2'))
sanyi
la source
2

Au cas où vous ne voudriez pas extraire une dépendance externe, voici ma tentative écrite pour Python 3.x.

rc, rel(et éventuellement on pourrait ajouter c) sont considérés comme "release candidate" et divisent le numéro de version en deux parties et s'il manque la valeur de la deuxième partie est élevée (999). Les autres lettres produisent une division et sont traitées comme des sous-numéros via le code de base 36.

import re
from itertools import chain
def compare_version(version1,version2):
    '''compares two version numbers
    >>> compare_version('1', '2') < 0
    True
    >>> compare_version('2', '1') > 0
    True
    >>> compare_version('1', '1') == 0
    True
    >>> compare_version('1.0', '1') == 0
    True
    >>> compare_version('1', '1.000') == 0
    True
    >>> compare_version('12.01', '12.1') == 0
    True
    >>> compare_version('13.0.1', '13.00.02') <0
    True
    >>> compare_version('1.1.1.1', '1.1.1.1') == 0
    True
    >>> compare_version('1.1.1.2', '1.1.1.1') >0
    True
    >>> compare_version('1.1.3', '1.1.3.000') == 0
    True
    >>> compare_version('3.1.1.0', '3.1.2.10') <0
    True
    >>> compare_version('1.1', '1.10') <0
    True
    >>> compare_version('1.1.2','1.1.2') == 0
    True
    >>> compare_version('1.1.2','1.1.1') > 0
    True
    >>> compare_version('1.2','1.1.1') > 0
    True
    >>> compare_version('1.1.1-rc2','1.1.1-rc1') > 0
    True
    >>> compare_version('1.1.1a-rc2','1.1.1a-rc1') > 0
    True
    >>> compare_version('1.1.10-rc1','1.1.1a-rc2') > 0
    True
    >>> compare_version('1.1.1a-rc2','1.1.2-rc1') < 0
    True
    >>> compare_version('1.11','1.10.9') > 0
    True
    >>> compare_version('1.4','1.4-rc1') > 0
    True
    >>> compare_version('1.4c3','1.3') > 0
    True
    >>> compare_version('2.8.7rel.2','2.8.7rel.1') > 0
    True
    >>> compare_version('2.8.7.1rel.2','2.8.7rel.1') > 0
    True

    '''
    chn = lambda x:chain.from_iterable(x)
    def split_chrs(strings,chars):
        for ch in chars:
            strings = chn( [e.split(ch) for e in strings] )
        return strings
    split_digit_char=lambda x:[s for s in re.split(r'([a-zA-Z]+)',x) if len(s)>0]
    splt = lambda x:[split_digit_char(y) for y in split_chrs([x],'.-_')]
    def pad(c1,c2,f='0'):
        while len(c1) > len(c2): c2+=[f]
        while len(c2) > len(c1): c1+=[f]
    def base_code(ints,base):
        res=0
        for i in ints:
            res=base*res+i
        return res
    ABS = lambda lst: [abs(x) for x in lst]
    def cmp(v1,v2):
        c1 = splt(v1)
        c2 = splt(v2)
        pad(c1,c2,['0'])
        for i in range(len(c1)): pad(c1[i],c2[i])
        cc1 = [int(c,36) for c in chn(c1)]
        cc2 = [int(c,36) for c in chn(c2)]
        maxint = max(ABS(cc1+cc2))+1
        return base_code(cc1,maxint) - base_code(cc2,maxint)
    v_main_1, v_sub_1 = version1,'999'
    v_main_2, v_sub_2 = version2,'999'
    try:
        v_main_1, v_sub_1 = tuple(re.split('rel|rc',version1))
    except:
        pass
    try:
        v_main_2, v_sub_2 = tuple(re.split('rel|rc',version2))
    except:
        pass
    cmp_res=[cmp(v_main_1,v_main_2),cmp(v_sub_1,v_sub_2)]
    res = base_code(cmp_res,max(ABS(cmp_res))+1)
    return res


import random
from functools import cmp_to_key
random.shuffle(versions)
versions.sort(key=cmp_to_key(compare_version))
Roland Puntaier
la source
1

La solution la plus difficile à lire, mais néanmoins une ligne! et en utilisant des itérateurs pour être rapide.

next((c for c in imap(lambda x,y:cmp(int(x or 0),int(y or 0)),
            v1.split('.'),v2.split('.')) if c), 0)

c'est-à-dire pour Python2.6 et 3. + btw, Python 2.5 et plus ancien doivent attraper le StopIteration.

Paul
la source
1

J'ai fait cela afin de pouvoir analyser et comparer la chaîne de version du paquet Debian. Veuillez noter que ce n'est pas strict avec la validation des caractères.

Cela pourrait également être utile:

#!/usr/bin/env python

# Read <https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version> for further informations.

class CommonVersion(object):
    def __init__(self, version_string):
        self.version_string = version_string
        self.tags = []
        self.parse()

    def parse(self):
        parts = self.version_string.split('~')
        self.version_string = parts[0]
        if len(parts) > 1:
            self.tags = parts[1:]


    def __lt__(self, other):
        if self.version_string < other.version_string:
            return True
        for index, tag in enumerate(self.tags):
            if index not in other.tags:
                return True
            if self.tags[index] < other.tags[index]:
                return True

    @staticmethod
    def create(version_string):
        return UpstreamVersion(version_string)

class UpstreamVersion(CommonVersion):
    pass

class DebianMaintainerVersion(CommonVersion):
    pass

class CompoundDebianVersion(object):
    def __init__(self, epoch, upstream_version, debian_version):
        self.epoch = epoch
        self.upstream_version = UpstreamVersion.create(upstream_version)
        self.debian_version = DebianMaintainerVersion.create(debian_version)

    @staticmethod
    def create(version_string):
        version_string = version_string.strip()
        epoch = 0
        upstream_version = None
        debian_version = '0'

        epoch_check = version_string.split(':')
        if epoch_check[0].isdigit():
            epoch = int(epoch_check[0])
            version_string = ':'.join(epoch_check[1:])
        debian_version_check = version_string.split('-')
        if len(debian_version_check) > 1:
            debian_version = debian_version_check[-1]
            version_string = '-'.join(debian_version_check[0:-1])

        upstream_version = version_string

        return CompoundDebianVersion(epoch, upstream_version, debian_version)

    def __repr__(self):
        return '{} {}'.format(self.__class__.__name__, vars(self))

    def __lt__(self, other):
        if self.epoch < other.epoch:
            return True
        if self.upstream_version < other.upstream_version:
            return True
        if self.debian_version < other.debian_version:
            return True
        return False


if __name__ == '__main__':
    def lt(a, b):
        assert(CompoundDebianVersion.create(a) < CompoundDebianVersion.create(b))

    # test epoch
    lt('1:44.5.6', '2:44.5.6')
    lt('1:44.5.6', '1:44.5.7')
    lt('1:44.5.6', '1:44.5.7')
    lt('1:44.5.6', '2:44.5.6')
    lt('  44.5.6', '1:44.5.6')

    # test upstream version (plus tags)
    lt('1.2.3~rc7',          '1.2.3')
    lt('1.2.3~rc1',          '1.2.3~rc2')
    lt('1.2.3~rc1~nightly1', '1.2.3~rc1')
    lt('1.2.3~rc1~nightly2', '1.2.3~rc1')
    lt('1.2.3~rc1~nightly1', '1.2.3~rc1~nightly2')
    lt('1.2.3~rc1~nightly1', '1.2.3~rc2~nightly1')

    # test debian maintainer version
    lt('44.5.6-lts1', '44.5.6-lts12')
    lt('44.5.6-lts1', '44.5.7-lts1')
    lt('44.5.6-lts1', '44.5.7-lts2')
    lt('44.5.6-lts1', '44.5.6-lts2')
    lt('44.5.6-lts1', '44.5.6-lts2')
    lt('44.5.6',      '44.5.6-lts1')
Pius Raeder
la source
0

Une autre solution:

def mycmp(v1, v2):
    import itertools as it
    f = lambda v: list(it.dropwhile(lambda x: x == 0, map(int, v.split('.'))[::-1]))[::-1]
    return cmp(f(v1), f(v2))

On peut utiliser comme ça aussi:

import itertools as it
f = lambda v: list(it.dropwhile(lambda x: x == 0, map(int, v.split('.'))[::-1]))[::-1]
f(v1) <  f(v2)
f(v1) == f(v2)
f(v1) >  f(v2)
pedrormjunior
la source
0

j'utilise celui-ci sur mon projet:

cmp(v1.split("."), v2.split(".")) >= 0
Keyrr Perino
la source
0

Des années plus tard, mais cette question est toujours au top.

Voici ma fonction de tri de version. Il divise la version en sections numériques et non numériques. Les nombres sont comparés en tant que intreste str(en tant que parties d'éléments de liste).

def sort_version_2(data):
    def key(n):
        a = re.split(r'(\d+)', n)
        a[1::2] = map(int, a[1::2])
        return a
    return sorted(data, key=lambda n: key(n))

Vous pouvez utiliser la fonction keycomme type de Versiontype personnalisé avec des opérateurs de comparaison. Si vous voulez vraiment l'utiliser, cmpvous pouvez le faire comme dans cet exemple: https://stackoverflow.com/a/22490617/9935708

def Version(s):
    s = re.sub(r'(\.0*)*$', '', s)  # to avoid ".0" at end
    a = re.split(r'(\d+)', s)
    a[1::2] = map(int, a[1::2])
    return a

def mycmp(a, b):
    a, b = Version(a), Version(b)
    return (a > b) - (a < b)  # DSM's answer

La suite de tests réussit.

Rysson
la source
-1

Ma solution préférée:

Remplir la chaîne avec des zéros supplémentaires et utiliser simplement les quatre premiers est facile à comprendre, ne nécessite aucune expression régulière et le lambda est plus ou moins lisible. J'utilise deux lignes pour la lisibilité, pour moi l'élégance est courte et simple.

def mycmp(version1,version2):
  tup = lambda x: [int(y) for y in (x+'.0.0.0.0').split('.')][:4]
  return cmp(tup(version1),tup(version2))
Daramarak
la source
-1

C'est ma solution (écrite en C, désolé). J'espère que vous le trouverez utile

int compare_versions(const char *s1, const char *s2) {
    while(*s1 && *s2) {
        if(isdigit(*s1) && isdigit(*s2)) {
            /* compare as two decimal integers */
            int s1_i = strtol(s1, &s1, 10);
            int s2_i = strtol(s2, &s2, 10);

            if(s1_i != s2_i) return s1_i - s2_i;
        } else {
            /* compare as two strings */
            while(*s1 && !isdigit(*s1) && *s2 == *s1) {
                s1++;
                s2++;
            }

            int s1_i = isdigit(*s1) ? 0 : *s1;
            int s2_i = isdigit(*s2) ? 0 : *s2;

            if(s1_i != s2_i) return s1_i - s2_i;
        }
    }

    return 0;
}
e_asphyx
la source