การเปรียบเทียบหมายเลขเวอร์ชันใน Python


99

ฉันต้องการที่จะเขียนcmpฟังก์ชั่นเหมือนที่เปรียบเทียบตัวเลขสองรุ่นและผลตอบแทน-1, 0หรือ1ขึ้นอยู่กับ valuses เทียบของพวกเขา

  • กลับมา-1หากเวอร์ชัน A เก่ากว่าเวอร์ชัน B
  • กลับมา0ถ้าเวอร์ชัน A และ B เทียบเท่ากัน
  • กลับมา1หากเวอร์ชัน A ใหม่กว่าเวอร์ชัน B

แต่ละส่วนย่อยควรตีความเป็นตัวเลขดังนั้น 1.10> 1.1

เอาต์พุตฟังก์ชันที่ต้องการคือ

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

และนี่คือการนำไปใช้งานของฉันเปิดให้ปรับปรุง:

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

ฉันใช้ Python 2.4.5 btw (ติดตั้งที่ที่ทำงานของฉัน ... )

นี่คือ 'ชุดทดสอบ' ขนาดเล็กที่คุณสามารถใช้ได้

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

ไม่ใช่คำตอบ แต่เป็นข้อเสนอแนะ - อาจคุ้มค่าที่จะใช้อัลกอริทึมของ Debian สำหรับการเปรียบเทียบหมายเลขเวอร์ชัน (โดยทั่วไปคือการสลับการเรียงลำดับส่วนที่ไม่ใช่ตัวเลขและตัวเลข) อัลกอริทึมอธิบายไว้ที่นี่ (เริ่มต้นที่ "สตริงถูกเปรียบเทียบจากซ้ายไปขวา")
hobbs

บลาร์ก. ชุดย่อยของ markdown ที่รองรับในความคิดเห็นไม่เคยทำให้ฉันสับสน ลิงก์ใช้งานได้แม้ว่ามันจะดูโง่
hobbs

ในกรณีที่ผู้อ่านในอนาคตต้องการสิ่งนี้สำหรับการแยกวิเคราะห์เวอร์ชันของตัวแทนผู้ใช้ฉันขอแนะนำให้ใช้ไลบรารีเฉพาะเนื่องจากรูปแบบทางประวัติศาสตร์กว้างเกินไป
James Broadhead


1
แม้ว่าคำถามในที่นี้จะเก่ากว่า แต่ดูเหมือนว่าคำถามอื่น ๆ นี้ได้รับการเจิมให้เป็นคำถามที่ยอมรับได้เนื่องจากหลายคำถามถูกปิดว่าซ้ำกับคำถามนั้น
John Y

คำตอบ:


37

ลบส่วนที่ไม่น่าสนใจของสตริง (ศูนย์และจุดต่อท้าย) จากนั้นเปรียบเทียบรายการตัวเลข

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

นี่เป็นแนวทางเดียวกับPär Wieslander แต่กะทัดรัดกว่าเล็กน้อย:

ต่อไปนี้คือการทดสอบบางส่วนขอบคุณ " วิธีเปรียบเทียบสองสตริงในรูปแบบเวอร์ชันที่คั่นด้วยจุดใน 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

2
ฉันกลัวว่ามันจะใช้ไม่ได้ผลrstrip(".0")จะเปลี่ยน ".10" เป็น ".1" ใน "1.0.10"
RedGlyph

ขออภัยด้วยฟังก์ชันของคุณ: mycmp ('1.1', '1.10') == 0
Johannes Charra

เมื่อใช้ regex ปัญหาดังกล่าวข้างต้นได้รับการแก้ไขแล้ว
gnud

ตอนนี้คุณได้รวมความคิดที่ดีทั้งหมดจากคนอื่น ๆ ลงในวิธีแก้ปัญหาของคุณแล้ว ... :-P ยังคงเป็นสิ่งที่ฉันจะทำหลังจากทั้งหมด ฉันจะยอมรับคำตอบนี้ ขอบคุณทุกคน
Johannes Charra

2
หมายเหตุ cmp () ถูกลบออกใน Python 3: docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
Dominic

281

แล้วการใช้ Python distutils.version.StrictVersionล่ะ?

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

ดังนั้นสำหรับcmpฟังก์ชันของคุณ:

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

หากคุณต้องการเปรียบเทียบหมายเลขเวอร์ชันที่มีความซับซ้อนมากขึ้นdistutils.version.LooseVersionจะมีประโยชน์มากกว่าอย่างไรก็ตามอย่าลืมเปรียบเทียบเฉพาะประเภทเดียวกันเท่านั้น

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

LooseVersion ไม่ใช่เครื่องมือที่ชาญฉลาดที่สุดและสามารถหลอกได้ง่ายๆ:

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

จะประสบความสำเร็จกับสายพันธุ์นี้คุณจะต้องไปยังขั้นตอนนอกห้องสมุดและการใช้มาตรฐานsetuptoolsparse_versionยูทิลิตี้แยก 's

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

ดังนั้นขึ้นอยู่กับการใช้งานในกรณีที่เฉพาะเจาะจงของคุณคุณจะต้องตัดสินใจว่า builtin เครื่องมือที่เพียงพอหรือถ้ามันรับประกันเพิ่มเป็นพึ่งพาdistutilssetuptools


2
ดูเหมือนจะสมเหตุสมผลที่สุดที่จะใช้สิ่งที่มีอยู่แล้ว :)
Patrick Wolf

2
ดี! คุณคิดออกโดยการอ่านแหล่งที่มาหรือไม่? ฉันไม่พบเอกสารสำหรับ distutils.version ทุกที่: - /
Adam Spiers

3
ทุกครั้งที่คุณไม่พบเอกสารประกอบให้ลองนำเข้าแพ็กเกจและใช้ help ()
rspeed

13
เป็น แต่ทราบว่าStrictVersion เฉพาะการทำงานที่มีถึงสามรุ่นจำนวน มันล้มเหลวสำหรับสิ่งที่ต้องการ0.4.3.6!
abergmeier

6
ทุกตัวอย่างของdistributeในคำตอบนี้ควรถูกแทนที่ด้วยsetuptoolsซึ่งมาพร้อมกับpkg_resourcesแพคเกจและมีตั้งแต่ ... เหมือนเคย ในทำนองเดียวกันนี้เป็นเอกสารที่เป็นทางการสำหรับฟังก์ชั่นที่มาพร้อมกับpkg_resources.parse_version() setuptools
Cecil Curry

30

การใช้ซ้ำถือเป็นความสง่างามในกรณีนี้หรือไม่? :)

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

7
อืมมันไม่สวยหรูนักเมื่อคุณอ้างถึงสิ่งที่อยู่นอกไลบรารีมาตรฐานโดยไม่ต้องอธิบายว่าจะหามาจากไหน ฉันส่งการแก้ไขเพื่อรวม URL โดยส่วนตัวแล้วฉันชอบใช้ distutils - ดูเหมือนจะไม่คุ้มค่ากับความพยายามที่จะดึงซอฟต์แวร์ของบุคคลที่สามมาใช้เพื่อให้งานง่าย ๆ
Adam Spires

1
@ อดัม - สเปียร์วุฒิ? คุณได้อ่านความเห็นหรือไม่? pkg_resourcesเป็นsetuptoolsแพ็คเกจที่รวมกลุ่ม เนื่องจากsetuptoolsมีผลบังคับใช้อย่างมีประสิทธิภาพในการติดตั้ง Python ทั้งหมดpkg_resourcesจึงสามารถใช้ได้ทุกที่ กล่าวได้ว่าdistutils.versionแพ็กเกจย่อยยังมีประโยชน์แม้ว่าจะฉลาดน้อยกว่าpkg_resources.parse_version()ฟังก์ชันระดับสูงกว่าก็ตาม ซึ่งคุณควรใช้ประโยชน์ได้ขึ้นอยู่กับระดับความวิกลจริตที่คุณคาดหวังในสตริงเวอร์ชัน
Cecil Curry

@CecilCurry ใช่แน่นอนฉันอ่านความคิดเห็น (ary) ซึ่งเป็นเหตุผลที่ฉันแก้ไขเพื่อให้ดีขึ้นแล้วระบุว่าฉันมี คุณคงไม่เห็นด้วยกับคำสั่งของฉันที่setuptoolsอยู่นอกไลบรารีมาตรฐานและแทนที่จะเป็นไปตามความต้องการของฉันdistutils ในกรณีนี้ คุณหมายถึงอะไรโดย "บังคับอย่างมีประสิทธิภาพ" และโปรดแสดงหลักฐานว่า "บังคับอย่างมีประสิทธิภาพ" เมื่อ 4.5 ปีก่อนเมื่อฉันเขียนความคิดเห็นนี้ได้หรือไม่
Adam Spires

12

ไม่จำเป็นต้องทำซ้ำบนสิ่งที่เพิ่มขึ้นของเวอร์ชัน ตัวดำเนินการเปรียบเทียบที่สร้างขึ้นในรายการและสิ่งที่เพิ่มขึ้นทำงานได้ตามที่คุณต้องการ คุณจะต้องเป็นศูนย์ขยายรายการเวอร์ชันให้มีความยาวที่สอดคล้องกัน ด้วย python 2.6 คุณสามารถใช้ izip_longest เพื่อเพิ่มลำดับ

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)

ด้วยเวอร์ชันที่ต่ำกว่าจำเป็นต้องมีการแฮ็กแผนที่

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)

เจ๋ง แต่เข้าใจยากสำหรับคนที่อ่านโค้ดไม่ได้เหมือนร้อยแก้ว :) ฉันคิดว่าคุณสามารถลดการแก้ปัญหาได้ในราคาที่อ่านได้เท่านั้น ...
Johannes Charra

10

นี่เล็กกว่าคำแนะนำของคุณเล็กน้อย แทนที่จะเติมรุ่นที่สั้นกว่าด้วยศูนย์ฉันจะลบเลขศูนย์ต่อท้ายออกจากรายการเวอร์ชันหลังจากแยก

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

ดีมากขอบคุณ แต่ฉันยังคงหวังว่าจะมีหนึ่งหรือสองซับ ... ;)
Johannes Charra

4
+1 @jellybean: สองสมุทรไม่ใช่สิ่งที่ดีที่สุดสำหรับการบำรุงรักษาและการอ่านง่ายเสมอไปอันนี้เป็นโค้ดที่ชัดเจนและกะทัดรัดในเวลาเดียวกันนอกจากนี้คุณสามารถใช้ซ้ำmycmpเพื่อวัตถุประสงค์อื่นในโค้ดของคุณได้หากต้องการ
RedGlyph

@RedGlyph: คุณมีจุดที่นั่น ควรจะพูดว่า "สองซับที่อ่านได้" :)
Johannes Charra

สวัสดี @ Pär Wieslander เมื่อฉันใช้วิธีนี้ในการแก้ปัญหาเดียวกันที่ปัญหา Leetcode ฉันได้รับข้อผิดพลาดในขณะที่วนรอบว่า "ดัชนีรายการอยู่นอกช่วง" คุณช่วยได้ไหมว่าทำไมมันถึงเกิดขึ้น นี่คือปัญหา: leetcode.com/explore/interview/card/amazon/76/array-and-strings/…
YouHaveaBigEgo

7

ลบการต่อท้าย.0และ.00ด้วย regex splitและใช้cmpฟังก์ชันที่เปรียบเทียบอาร์เรย์อย่างถูกต้อง:

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)

และแน่นอนคุณสามารถแปลงเป็นซับเดียวได้หากคุณไม่สนใจเส้นยาว


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

มันเป็นซับเส้นเดียว (แยกเพื่อให้อ่านง่าย) ไม่แน่ใจว่าอ่านได้ ...


1
ใช่ และหดลงไปอีก ( tupleไม่จำเป็นต้องใช้ btw):cmp(*zip(*map(lambda x,y:(x or 0,y or 0), map(int,v1.split('.')), map(int,v2.split('.')) )))
พอล

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

ใช้งานสำหรับ php version_compareยกเว้น "=" เพราะมันคลุมเครือ


2

รายการเปรียบได้ใน Python ดังนั้นหากมีคนแปลงสตริงที่แสดงตัวเลขเป็นจำนวนเต็มการเปรียบเทียบ Python พื้นฐานสามารถใช้ได้กับความสำเร็จ

ฉันต้องการขยายแนวทางนี้อีกเล็กน้อยเพราะฉันใช้ Python3x ซึ่งcmpไม่มีฟังก์ชันนี้อีกต่อไป ฉันมีที่จะเลียนแบบด้วยcmp(a,b) (a > b) - (a < b)และหมายเลขเวอร์ชันไม่สะอาดเลยและอาจมีอักขระที่เป็นตัวเลขและตัวอักษรอื่น ๆ ทุกประเภท มีหลายกรณีที่ฟังก์ชันไม่สามารถบอกลำดับได้ดังนั้นจึงส่งกลับFalse(ดูตัวอย่างแรก)

ดังนั้นฉันจึงโพสต์สิ่งนี้แม้ว่าคำถามจะเก่าและมีคำตอบแล้วก็ตามเพราะอาจช่วยชีวิตคนได้ไม่กี่นาที

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

2

ในกรณีที่คุณไม่ต้องการดึงการพึ่งพาภายนอกนี่คือความพยายามของฉันที่เขียนสำหรับ Python 3.x.

rc, rel(และอาจเพิ่มได้c) ถือเป็น "ผู้สมัครรุ่น" และแบ่งหมายเลขเวอร์ชันออกเป็นสองส่วนและหากไม่มีค่าของส่วนที่สองจะสูง (999) ตัวอักษรอื่นจะสร้างการแบ่งและแจกแจงเป็นตัวเลขย่อยผ่านรหัสฐาน -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))

1

วิธีแก้ปัญหาที่อ่านยากที่สุด แต่ยังมีซับเดียว! และใช้ตัววนซ้ำเพื่อให้รวดเร็ว

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)

นั่นคือสำหรับ Python2.6 และ 3 + btw, Python 2.5 และเก่ากว่าจำเป็นต้องจับ StopIteration


1

ฉันทำสิ่งนี้เพื่อให้สามารถแยกวิเคราะห์และเปรียบเทียบสตริงเวอร์ชันแพ็คเกจ Debian โปรดสังเกตว่าไม่ได้เข้มงวดกับการตรวจสอบอักขระ

สิ่งนี้อาจเป็นประโยชน์เช่นกัน:

#!/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')

0

วิธีแก้ปัญหาอื่น:

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

สามารถใช้ได้เช่นกัน:

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)


0

หลายปีต่อมา แต่หยุดคำถามนี้ไว้ด้านบน

นี่คือฟังก์ชันการเรียงลำดับเวอร์ชันของฉัน มันแบ่งรุ่นออกเป็นส่วนตัวเลขและส่วนที่ไม่ใช่ตัวเลข ตัวเลขจะถูกเปรียบเทียบเป็นintส่วนที่เหลือstr(เป็นส่วนหนึ่งของรายการ)

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

คุณสามารถใช้ฟังก์ชันkeyเป็นประเภทที่กำหนดเองVersionโดยใช้ตัวดำเนินการเปรียบเทียบ หากต้องการใช้งานจริงๆcmpคุณสามารถทำได้ตามตัวอย่างนี้: 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

ชุดทดสอบผ่าน


-1

ทางออกที่ฉันต้องการ:

การเติมสตริงด้วยศูนย์พิเศษและเพียงแค่ใช้สี่ตัวแรกก็เข้าใจง่ายไม่ต้องใช้ regex ใด ๆ และแลมบ์ดาอ่านได้มากหรือน้อย ฉันใช้สองบรรทัดเพื่อให้อ่านง่ายสำหรับฉันความสง่างามนั้นสั้นและเรียบง่าย

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

-1

นี่คือวิธีแก้ปัญหาของฉัน (เขียนด้วยภาษา C ขออภัย) ฉันหวังว่าคุณจะพบว่ามีประโยชน์

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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.