Versionsnummernvergleich in Python

98

Ich möchte einen schreiben cmp-ähnliche Funktion , die zwei Versionsnummern und kehren vergleicht -1, 0oder 1auf der Grundlage ihres im Vergleich valuses.

  • Rückgabe, -1wenn Version A älter als Version B ist
  • Rückgabe, 0wenn Version A und B gleichwertig sind
  • Rückgabe, 1wenn Version A neuer als Version B ist

Jeder Unterabschnitt soll als Zahl interpretiert werden, daher 1.10> 1.1.

Gewünschte Funktionsausgänge sind

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
...

Und hier ist meine Implementierung, die offen für Verbesserungen ist:

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

Ich benutze übrigens Python 2.4.5. (an meinem Arbeitsplatz installiert ...).

Hier ist eine kleine Testsuite, die Sie verwenden können

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
quelle
Keine Antwort, sondern ein Vorschlag - es könnte sich lohnen, den Debian-Algorithmus für den Versionsnummernvergleich zu implementieren (im Grunde abwechselndes Sortieren von nicht numerischen und numerischen Teilen). Der Algorithmus wird hier beschrieben (beginnend mit "Die Zeichenfolgen werden von links nach rechts verglichen").
Hobbs
Blargh. Die in Kommentaren unterstützte Teilmenge des Abschlags verwirrt mich immer wieder. Der Link funktioniert trotzdem, auch wenn er dumm aussieht.
Hobbs
Für den Fall, dass zukünftige Leser dies für das Parsen von User-Agent-Versionen benötigen, empfehle ich eine dedizierte Bibliothek, da die historische Variation zu groß ist.
James Broadhead
2
Mögliches Duplikat von Versionszeichenfolgen in Python vergleichen
John Y
1
Obwohl die Frage hier älter ist, scheint diese andere Frage als die kanonische Frage gesalbt worden zu sein, da viele, viele Fragen als Duplikate dieser Frage geschlossen sind.
John Y

Antworten:

36

Entfernen Sie den uninteressanten Teil der Zeichenfolge (nachgestellte Nullen und Punkte) und vergleichen Sie dann die Zahlenlisten.

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))

Dies ist der gleiche Ansatz wie bei Pär Wieslander, jedoch etwas kompakter:

Hier sind einige Tests dank " Wie vergleiche ich zwei Zeichenfolgen im punktgetrennten Versionsformat in 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
quelle
2
Ich fürchte, es wird nicht funktionieren, das rstrip(".0")wird ".10" in ".1" in "1.0.10" ändern.
RedGlyph
Entschuldigung, aber mit Ihrer Funktion: mycmp ('1.1', '1.10') == 0
Johannes Charra
Mit der Verwendung von Regex ist das oben erwähnte Problem behoben.
Gnud
Jetzt haben Sie alle guten Ideen der anderen in Ihre Lösung integriert ... :-P Trotzdem ist dies so ziemlich das, was ich schließlich tun würde. Ich werde diese Antwort akzeptieren. Vielen Dank an alle
Johannes Charra
2
Hinweis cmp () wurde in Python 3 entfernt: docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
Dominic Cleal
279

Wie wäre es mit Pythons distutils.version.StrictVersion?

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

Also für Ihre cmpFunktion:

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

Wenn Sie komplexere Versionsnummern vergleichen möchten, distutils.version.LooseVersionist dies nützlicher. Vergleichen Sie jedoch nur dieselben Typen.

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

LooseVersion ist nicht das intelligenteste Werkzeug und kann leicht ausgetrickst werden:

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

Um mit dieser Rasse erfolgreich zu sein, müssen Sie die Standardbibliothek verlassen und das Parsing-Dienstprogramm von setuptools verwendenparse_version .

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

Abhängig von Ihrem spezifischen Anwendungsfall müssen Sie entscheiden, ob die integrierten distutilsTools ausreichen oder ob das Hinzufügen als Abhängigkeit gerechtfertigt ist setuptools.

bradley.ayers
quelle
2
scheint am sinnvollsten zu sein, nur das zu verwenden, was bereits da ist :)
Patrick Wolf
2
Nett! Haben Sie dies durch Lesen der Quelle herausgefunden? Ich kann nirgendwo Dokumente für distutils.version finden: - /
Adam Spiers
3
Wenn Sie keine Dokumentation finden, importieren Sie das Paket und verwenden Sie help ().
Geschwindigkeit
13
Beachten Sie jedoch, dass StrictVersion NUR mit einer Version mit bis zu drei Zahlen funktioniert. Es scheitert an Dingen wie 0.4.3.6!
Abergmeier
6
Jede Instanz distributein dieser Antwort sollte durch ersetzt werden setuptools, die im pkg_resourcesLieferumfang des Pakets enthalten ist und seitdem ... wie immer . Ebenso ist dies die offizielle Dokumentation für die mit pkg_resources.parse_version()gebündelte Funktion setuptools.
Cecil Curry
30

Wird Wiederverwendung in diesem Fall als Eleganz angesehen? :) :)

# 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
quelle
7
Hmm, es ist nicht so elegant, wenn Sie auf etwas außerhalb der Standardbibliothek verweisen, ohne zu erklären, wo Sie es bekommen können. Ich habe eine Bearbeitung eingereicht, um die URL einzuschließen. Persönlich bevorzuge ich die Verwendung von Distutils - es scheint nicht die Mühe wert zu sein, Software von Drittanbietern für eine so einfache Aufgabe zu verwenden.
Adam Spires
1
@ Adam-Türme wut? Hast du den Kommentar überhaupt gelesen? pkg_resourcesist ein setuptoolsgebündeltes Paket. Da dies setuptoolsbei allen Python-Installationen effektiv obligatorisch pkg_resourcesist , ist es effektiv überall verfügbar. Das distutils.versionUnterpaket ist jedoch auch nützlich - wenn auch wesentlich weniger intelligent als die übergeordnete pkg_resources.parse_version()Funktion. Was Sie nutzen sollten, hängt davon ab, welchen Grad an Wahnsinn Sie in Versionszeichenfolgen erwarten.
Cecil Curry
@CecilCurry Ja, natürlich habe ich den Kommentar (ary) gelesen, weshalb ich ihn bearbeitet habe, um ihn besser zu machen, und dann angegeben, dass ich ihn hatte. Vermutlich widersprechen Sie nicht meiner Aussage, setuptoolsdie außerhalb der Standardbibliothek liegt, und stattdessen meiner distutils in diesem Fall angegebenen Präferenz für . Was genau meinen Sie mit "effektiv obligatorisch", und können Sie bitte nachweisen, dass es vor 4,5 Jahren "effektiv obligatorisch" war, als ich diesen Kommentar schrieb?
Adam Spires
12

Sie müssen die Versionstupel nicht durchlaufen. Der eingebaute Vergleichsoperator für Listen und Tupel funktioniert bereits genau so, wie Sie es möchten. Sie müssen nur die Versionslisten auf Null auf die entsprechende Länge erweitern. Mit Python 2.6 können Sie die Sequenzen mit izip_longest auffüllen.

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)

Bei niedrigeren Versionen ist etwas Kartenhackery erforderlich.

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)
Ameisen Aasma
quelle
Cool, aber schwer zu verstehen für jemanden, der Code nicht wie Prosa lesen kann. :) Nun, ich gehe davon aus, dass Sie die Lösung nur auf Kosten der Lesbarkeit verkürzen können ...
Johannes Charra
10

Dies ist etwas kompakter als Ihr Vorschlag. Anstatt die kürzere Version mit Nullen zu füllen, entferne ich nach dem Teilen nachfolgende Nullen aus den Versionslisten.

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
quelle
Schön, danke. Aber ich hoffe immer noch auf ein oder zwei Liner ...;)
Johannes Charra
4
+1 @jellybean: Zwei-Liner sind nicht immer die besten für Wartung und Lesbarkeit. Dieser Code ist sehr klar und kompakt zugleich. Außerdem können Sie ihn bei Bedarf mycmpfür andere Zwecke in Ihrem Code wiederverwenden .
RedGlyph
@ RedGlyph: Da hast du Recht. Hätte sagen sollen "ein lesbarer Zweiliner". :)
Johannes Charra
hi @ Pär Wieslander, wenn ich diese Lösung verwende, um das gleiche Problem beim Leetcode-Problem zu lösen, erhalte ich eine Fehlermeldung in der while-Schleife mit der Aufschrift "Listenindex außerhalb des Bereichs". Können Sie bitte helfen, warum das passiert? Hier ist das Problem: leetcode.com/explore/interview/card/amazon/76/array-and-strings/…
YouHaveaBigEgo
7

Entfernen Sie Trailing .0und .00mit Regex splitund verwenden Sie die cmpFunktion, mit der Arrays korrekt verglichen werden:

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)

Und natürlich können Sie es in einen Einzeiler umwandeln, wenn Ihnen die langen Schlangen nichts ausmachen.

yu_sha
quelle
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('.')]))))

Es ist ein Einzeiler (aus Gründen der Lesbarkeit aufgeteilt). Nicht sicher über lesbar ...

mavnn
quelle
1
Ja! Und noch weiter geschrumpft ( tuplewird übrigens nicht benötigt):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

Implementiere für PHP version_compare, außer "=". Weil es mehrdeutig ist.

Ryan Fau
quelle
2

Listen sind in Python vergleichbar. Wenn also jemand die Zeichenfolgen, die die Zahlen darstellen, in Ganzzahlen konvertiert, kann der grundlegende Python-Vergleich mit Erfolg verwendet werden.

Ich musste diesen Ansatz etwas erweitern, da ich Python3x verwende, wo die cmpFunktion nicht mehr existiert. Ich hatte zu emulieren cmp(a,b)mit (a > b) - (a < b). Und Versionsnummern sind überhaupt nicht so sauber und können alle Arten anderer alphanumerischer Zeichen enthalten. Es gibt Fälle, in denen die Funktion die Reihenfolge nicht erkennen kann, sodass sie zurückkehrt False(siehe das erste Beispiel).

Ich poste dies auch dann, wenn die Frage alt und bereits beantwortet ist, da dies einige Minuten im Leben eines Menschen sparen kann.

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
quelle
2

Für den Fall, dass Sie keine externe Abhängigkeit ziehen möchten, ist hier mein Versuch für Python 3.x geschrieben.

rc, rel(und möglicherweise könnte man hinzufügen c) gelten als "Release Candidate" und teilen die Versionsnummer in zwei Teile und wenn der Wert fehlt, ist der Wert des zweiten Teils hoch (999). Andere Buchstaben erzeugen eine Aufteilung und werden als Subnummern über den Basis-36-Code behandelt.

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
quelle
1

Die am schwersten zu lesende Lösung, aber dennoch ein Einzeiler! und Iteratoren verwenden, um schnell zu sein.

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)

Das ist für Python2.6 und 3. + Übrigens müssen Python 2.5 und ältere die StopIteration abfangen.

Paul
quelle
1

Ich habe dies getan, um die Versionszeichenfolge des Debian-Pakets analysieren und vergleichen zu können. Bitte beachten Sie, dass die Zeichenvalidierung nicht streng ist.

Dies könnte ebenfalls hilfreich sein:

#!/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
quelle
0

Eine andere Lösung:

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))

Man kann auch so verwenden:

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
quelle
0

Ich benutze dieses für mein Projekt:

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

Jahre später steht diese Frage aber ganz oben.

Hier ist meine Versionssortierfunktion. Die Version wird in Abschnitte mit und ohne Zahlen aufgeteilt. Zahlen werden als intRest wie str(als Teile von Listenelementen) verglichen .

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))

Sie können die Funktion keyals eine Art benutzerdefinierten VersionTyp mit Vergleichsoperatoren verwenden. Wenn Sie es wirklich verwenden möchten, cmpkönnen Sie es wie in diesem Beispiel tun: 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

Testsuite besteht.

Rysson
quelle
-1

Meine bevorzugte Lösung:

Das Auffüllen der Zeichenfolge mit zusätzlichen Nullen und die Verwendung der ersten vier ist leicht zu verstehen, erfordert keinen regulären Ausdruck und das Lambda ist mehr oder weniger lesbar. Ich benutze zwei Zeilen für die Lesbarkeit, für mich ist Eleganz kurz und einfach.

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
quelle
-1

Dies ist meine Lösung (geschrieben in C, sorry). Ich hoffe, Sie finden es nützlich

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
quelle