Porównanie numerów wersji w Pythonie

98

Chcę napisać cmp-Jak funkcję, która porównuje dwa numery wersji i zwrotów -1, 0albo 1na podstawie ich stosunku valuses.

  • Zwróć, -1jeśli wersja A jest starsza niż wersja B.
  • Zwróć, 0jeśli wersje A i B są równoważne
  • Zwróć, 1jeśli wersja A jest nowsza niż wersja B.

Każda podsekcja ma być interpretowana jako liczba, dlatego 1,10> 1,1.

Wymagane wyjścia funkcji to

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

A oto moja implementacja, otwarta na ulepszenia:

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

Przy okazji używam Pythona 2.4.5. (zainstalowany w moim miejscu pracy ...).

Oto mały „zestaw testów”, którego możesz użyć

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
źródło
To nie jest odpowiedź, ale sugestia - warto byłoby zaimplementować algorytm Debiana do porównywania numerów wersji (w zasadzie naprzemienne sortowanie części nienumerycznych i numerycznych). Algorytm jest opisany tutaj (zaczynając od „Ciągi znaków są porównywane od lewej do prawej”).
Hobbs
Blargh. Podzbiór przecen obsługiwanych w komentarzach nigdy nie przestaje mnie mylić. Link i tak działa, nawet jeśli wygląda głupio.
Hobbs
Na wypadek, gdyby przyszli czytelnicy potrzebowali tego do analizy wersji klienta użytkownika, polecam dedykowaną bibliotekę, ponieważ wariacja historyczna jest zbyt szeroka.
James Broadhead,
2
Możliwy duplikat ciągów porównawczych wersji w Pythonie
John Y
1
Chociaż pytanie tutaj jest starsze, wydaje się, że to drugie pytanie zostało namaszczone jako pytanie kanoniczne, ponieważ wiele, wiele pytań jest zamkniętych jako duplikaty tego pytania.
John Y

Odpowiedzi:

36

Usuń nieciekawą część ciągu (końcowe zera i kropki), a następnie porównaj listy liczb.

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

To jest to samo podejście, co Pär Wieslander, ale nieco bardziej zwarte:

Oto kilka testów dzięki „ Jak porównać dwa ciągi znaków w formacie wersji rozdzielanej kropkami w 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
źródło
2
Obawiam się, że to nie zadziała, rstrip(".0")zmieni się „.10” na „.1” w „1.0.10”.
RedGlyph
Przepraszamy, ale z twoją funkcją: mycmp ('1.1', '1.10') == 0
Johannes Charra
Dzięki użyciu wyrażenia regularnego problem wspomniany powyżej został rozwiązany.
gnud
Teraz połączyłeś wszystkie dobre pomysły z innych w swoje rozwiązanie ... :-P nadal, to jest właściwie to, co bym zrobił. Przyjmuję tę odpowiedź. Dzięki wszystkim
Johannes Charra
2
Uwaga cmp () została usunięta w Pythonie 3: docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
Dominic Cleal
279

A co powiesz na używanie Pythona distutils.version.StrictVersion?

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

Więc dla twojej cmpfunkcji:

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

Jeśli chcesz porównać numery wersji, które są bardziej złożone, distutils.version.LooseVersionbędą bardziej przydatne, jednak pamiętaj, aby porównać tylko te same typy.

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

LooseVersion nie jest najbardziej inteligentnym narzędziem i można go łatwo oszukać:

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

Aby odnieść sukces z tą rasą, musisz wyjść poza standardową bibliotekę i użyć narzędzia do parsowania setuptoolsparse_version .

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

W zależności od konkretnego przypadku użycia, musisz zdecydować, czy wbudowane distutilsnarzędzia są wystarczające, czy też uzasadnione jest dodanie jako zależności setuptools.

bradley.ayers
źródło
2
wydaje się, że najbardziej sensowne jest po prostu użycie tego, co już jest :)
Patrick Wolf
2
Miły! Czy odkryłeś to, czytając źródło? Nigdzie nie mogę znaleźć dokumentacji dla distutils.version: - /
Adam Spiers
3
Za każdym razem, gdy nie możesz znaleźć dokumentacji, spróbuj zaimportować pakiet i użyj help ().
rspeed
13
Należy jednak pamiętać, że działa StrictVersion TYLKO z wersją do trzech numerów. Nie udaje się na przykład 0.4.3.6!
abergmeier
6
Każde wystąpienie distributew tej odpowiedzi powinno zostać zastąpione przez setuptools, który jest dostarczany w pkg_resourcespakiecie i od tego czasu ... jak zawsze . Podobnie, jest to oficjalna dokumentacja dla pkg_resources.parse_version()funkcji w pakiecie z setuptools.
Cecil Curry
30

Czy ponowne użycie jest w tym przypadku uważane za elegancję? :)

# 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
źródło
7
Hmm, nie jest tak elegancko, gdy odnosisz się do czegoś spoza standardowej biblioteki bez wyjaśnienia, skąd to wziąć. Przesłałem edycję, aby dołączyć adres URL. Osobiście wolę używać distutils - nie wydaje się warte wysiłku pobieranie oprogramowania innej firmy do tak prostego zadania.
Adam Spiers
1
@ adam-spires wut? Czy w ogóle przeczytałeś komentarz? pkg_resourcesjest setuptoolspakietem w pakiecie. Ponieważ setuptoolsjest obowiązkowe we wszystkich instalacjach Pythona, pkg_resourcesjest efektywnie dostępne wszędzie. To powiedziawszy, distutils.versionpodpakiet jest również przydatny - choć znacznie mniej inteligentny niż funkcja wyższego poziomu pkg_resources.parse_version(). To, które powinieneś wykorzystać, zależy od tego, jakiego stopnia szaleństwa oczekujesz od ciągów wersji.
Cecil Curry
@CecilCurry Tak, oczywiście, przeczytałem komentarz (ary), dlatego zredagowałem go, aby był lepszy, a potem stwierdziłem, że tak. Przypuszczalnie nie zgadzasz się z moim stwierdzeniem, które nie setuptoolsznajduje się w standardowej bibliotece, a zamiast tego z moją deklarowaną preferencją distutils w tym przypadku . Więc co dokładnie rozumiesz przez „skutecznie obowiązkowe” i czy możesz przedstawić dowody, że było to „faktycznie obowiązkowe” 4,5 roku temu, kiedy pisałem ten komentarz?
Adam Spiers
12

Nie ma potrzeby iteracji po krotkach wersji. Wbudowany operator porównania na listach i krotkach już działa dokładnie tak, jak chcesz. Wystarczy zerować, aby rozszerzyć listy wersji do odpowiedniej długości. W Pythonie 2.6 możesz użyć izip_longest do wypełnienia sekwencji.

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)

W przypadku niższych wersji wymagane jest trochę hakowania map.

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)
Ants Aasma
źródło
Fajne, ale trudne do zrozumienia dla kogoś, kto nie potrafi czytać kodu jak proza. :) Cóż, zakładam, że można skrócić rozwiązanie kosztem czytelności ...
Johannes Charra
10

To jest trochę bardziej zwięzłe niż twoja sugestia. Zamiast wypełniać krótszą wersję zerami, usuwam końcowe zera z list wersji po podzieleniu.

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
źródło
Niezły, dzięki. Ale wciąż liczę na jedno lub dwu liniowiec ...;)
Johannes Charra
4
+1 @jellybean: dwuwierszówki nie zawsze są najlepsze do konserwacji i czytelności, ten jest bardzo przejrzystym i jednocześnie kompaktowym kodem, poza tym możesz go ponownie wykorzystać mycmpdo innych celów w swoim kodzie, jeśli tego potrzebujesz.
RedGlyph
@RedGlyph: Masz rację. Powinienem był powiedzieć „czytelny dwuwierszowiec”. :)
Johannes Charra
cześć @ Pär Wieslander, kiedy używam tego rozwiązania do rozwiązania tego samego problemu z Leetcode, pojawia się błąd w pętli while z komunikatem „lista indeksów poza zakresem”. Czy możesz mi pomóc, dlaczego tak się dzieje? Oto problem: leetcode.com/explore/interview/card/amazon/76/array-and-strings/…
YouHaveaBigEgo
7

Usuń końcowe .0i .00z wyrażeniem regularnym spliti użyj cmpfunkcji, która poprawnie porównuje tablice:

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)

I oczywiście możesz przekształcić go w jednolinijkowy, jeśli nie przeszkadzają ci długie linie.

yu_sha
źródło
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('.')]))))

To jedna wkładka (podzielona dla czytelności). Nie jestem pewien co do czytelności ...

mavnn
źródło
1
Tak! I skurczył się jeszcze bardziej (przy tupleokazji nie jest potrzebny):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

Zaimplementuj dla php version_compare, z wyjątkiem „=”. Ponieważ jest niejednoznaczne.

Ryan Fau
źródło
2

Listy są porównywalne w Pythonie, więc jeśli ktoś zamieni ciągi reprezentujące liczby na liczby całkowite, z powodzeniem można zastosować podstawowe porównanie Pythona.

Musiałem nieco rozszerzyć to podejście, ponieważ używam Python3x, w którym cmpfunkcja już nie istnieje. Musiałem naśladować cmp(a,b)z (a > b) - (a < b). Numery wersji nie są wcale takie czyste i mogą zawierać wszelkiego rodzaju inne znaki alfanumeryczne. Są przypadki, gdy funkcja nie może podać kolejności, więc zwraca False(zobacz pierwszy przykład).

Więc piszę to, nawet jeśli pytanie jest stare i już odpowiedziałem, bo może to zaoszczędzić kilka minut w czyimś życiu.

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
źródło
2

Jeśli nie chcesz wciągać zewnętrznej zależności, oto moja próba napisana dla Pythona 3.x.

rc, rel(i być może można by dodać c) są uważane za „kandydatów do wydania” i dzielą numer wersji na dwie części, a jeśli ich brakuje, wartość drugiej części jest wysoka (999). W przeciwnym razie litery powodują podział i są traktowane jako liczby podrzędne za pośrednictwem kodu 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
źródło
1

Najtrudniejsze do odczytania rozwiązanie, ale jednak jednolinijkowe! i używając iteratorów, aby działać szybko.

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)

to jest dla Python2.6 i 3. + btw, Python 2.5 i starsze muszą przechwytywać StopIteration.

Paweł
źródło
1

Zrobiłem to, aby móc przeanalizować i porównać ciąg znaków wersji pakietu Debiana. Proszę zauważyć, że nie jest to ścisłe przy sprawdzaniu poprawności znaków.

Może to być również pomocne:

#!/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
źródło
0

Inne rozwiązanie:

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

Można też użyć w ten sposób:

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
źródło
0

używam tego w moim projekcie:

cmp(v1.split("."), v2.split(".")) >= 0
Keyrr Perino
źródło
0

Wiele lat później, ale nadal to pytanie jest na topie.

Oto moja funkcja sortowania wersji. Dzieli wersję na sekcje liczbowe i nieliczbowe. Liczby są porównywane z intresztą jako str(jako części elementów listy).

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

Funkcji można używać keyjako typu niestandardowego Versionz operatorami porównania. Jeśli naprawdę chcesz skorzystać cmp, możesz to zrobić jak w tym przykładzie: 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

Pakiet testów przechodzi pomyślnie.

rysson
źródło
-1

Moje preferowane rozwiązanie:

Uzupełnienie łańcucha dodatkowymi zerami i użycie samych czterech pierwszych jest łatwe do zrozumienia, nie wymaga żadnego wyrażenia regularnego, a lambda jest mniej lub bardziej czytelna. Dla czytelności używam dwóch linijek, dla mnie elegancja jest krótka i prosta.

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
źródło
-1

To jest moje rozwiązanie (napisane w C, przepraszam). Mam nadzieję, że okażą się przydatne

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
źródło