So sánh số phiên bản bằng Python


98

Tôi muốn viết một cmpchức năng -like mà so sánh hai số phiên bản và lợi nhuận -1, 0hoặc 1dựa trên valuses so họ.

  • Quay lại -1nếu phiên bản A cũ hơn phiên bản B
  • Trả lại 0nếu phiên bản A và B tương đương
  • Quay lại 1nếu phiên bản A mới hơn phiên bản B

Mỗi tiểu mục được hiểu là một số, do đó 1.10> 1.1.

Đầu ra chức năng mong muốn là

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

Và đây là cách triển khai của tôi, đang mở để cải thiện:

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

Tôi đang sử dụng Python 2.4.5 btw. (được lắp đặt tại nơi làm việc của tôi ...).

Đây là một 'bộ thử nghiệm' nhỏ mà bạn có thể sử dụng

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

Không phải là một câu trả lời mà là một gợi ý - nó có thể đáng được triển khai thuật toán Debian để so sánh số phiên bản (về cơ bản, sắp xếp xen kẽ các phần không phải số và số). Thuật toán được mô tả ở đây (bắt đầu từ "Các chuỗi được so sánh từ trái sang phải").
hobbs

Blargh. Tập hợp con của đánh dấu được hỗ trợ trong các nhận xét không bao giờ làm tôi bối rối. Liên kết vẫn hoạt động, ngay cả khi nó trông ngu ngốc.
hobbs

Trong trường hợp người đọc trong tương lai cần điều này để phân tích cú pháp phiên bản tác nhân người dùng, tôi khuyên bạn nên sử dụng một thư viện chuyên dụng vì biến thể lịch sử của nó quá rộng.
James Broadhead


1
Mặc dù câu hỏi ở đây cũ hơn, nhưng có vẻ như câu hỏi khác này đã được xức dầu là câu hỏi kinh điển, vì rất nhiều câu hỏi được đóng lại như là bản sao của câu hỏi đó.
John Y

Câu trả lời:


36

Loại bỏ phần không quan tâm của chuỗi (số 0 ở cuối và dấu chấm), sau đó so sánh danh sách các số.

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

Đây là cách tiếp cận tương tự như Pär Wieslander, nhưng nhỏ gọn hơn một chút:

Dưới đây là một số bài kiểm tra, nhờ vào " Cách so sánh hai chuỗi ở định dạng phiên bản được phân tách bằng dấu chấm trong 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
Tôi e rằng nó sẽ không hoạt động, rstrip(".0")sẽ thay đổi ".10" thành ".1" trong "1.0.10".
RedGlyph

Xin lỗi, nhưng với hàm của bạn: mycmp ('1.1', '1.10') == 0
Johannes Charra

Với việc sử dụng regex, sự cố nêu trên đã được khắc phục.
gnud

Bây giờ bạn đã kết hợp tất cả những ý tưởng hay từ những người khác vào giải pháp của mình ... :-P vẫn còn, đây là điều tôi muốn làm. Tôi sẽ chấp nhận câu trả lời này. Cảm ơn tất cả mọi người
Johannes Charra

2
Lưu ý CMP () đã được gỡ bỏ bằng Python 3: docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons
Dominic Cleal

279

Làm thế nào về việc sử dụng Python distutils.version.StrictVersion?

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

Vì vậy, đối với cmpchức năng của bạn :

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

Nếu bạn muốn so sánh các số phiên bản phức tạp hơn distutils.version.LooseVersionsẽ hữu ích hơn, tuy nhiên hãy nhớ chỉ so sánh các loại cùng loại.

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

LooseVersion không phải là công cụ thông minh nhất và có thể dễ dàng bị lừa:

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

Để thành công với giống chó này, bạn cần phải bước ra ngoài thư viện tiêu chuẩn và sử dụng tiện ích phân tích cú pháp của setuptoolsparse_version .

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

Vì vậy, tùy thuộc vào trường hợp sử dụng cụ thể của bạn, bạn sẽ cần quyết định xem liệu các distutilscông cụ tích hợp có đủ hay không hoặc liệu nó có được bảo đảm để thêm vào như một phần phụ thuộc hay không setuptools.


2
dường như làm cho ý nghĩa nhất để chỉ sử dụng những gì đã có :)
Patrick Wolf

2
Đẹp! Bạn có tìm ra điều này bằng cách đọc nguồn không? Tôi không thể tìm thấy tài liệu cho distutils.version ở bất kỳ đâu: - /
Adam Spiers

3
Bất kỳ lúc nào bạn không thể tìm thấy tài liệu, hãy thử nhập gói và sử dụng trợ giúp ().
rspeed

13
Tuy nhiên, hãy lưu ý rằng điều đó StrictVersion CHỈ hoạt động với tối đa phiên bản ba số. Nó không thành công cho những thứ như 0.4.3.6!
abergmeier

6
Mọi trường hợp distributetrong câu trả lời này nên được thay thế bằng setuptools, đi kèm với pkg_resourcesgói và từ đó ... giống như, mãi mãi . Tương tự như vậy, đây là tài liệu chính thức cho pkg_resources.parse_version()chức năng đi kèm setuptools.
Cecil Curry,

30

Việc sử dụng lại có được coi là sang trọng trong trường hợp này không? :)

# 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
Hmm, thật không thanh lịch khi bạn tham khảo thứ gì đó bên ngoài thư viện tiêu chuẩn mà không giải thích nơi lấy nó. Tôi đã gửi một chỉnh sửa để bao gồm URL. Cá nhân tôi thích sử dụng các bản phân phối hơn - có vẻ như không đáng để cố gắng sử dụng phần mềm của bên thứ 3 cho một nhiệm vụ đơn giản như vậy.
Adam Spiers

1
@ adam- spiers wut? Bạn thậm chí đã đọc bình luận? pkg_resourceslà một setuptoolsgói -bundled. Vì setuptoolslà bắt buộc đối với tất cả các cài đặt Python, nên pkg_resourcesnó có sẵn ở mọi nơi. Điều đó nói rằng, distutils.versiongói con cũng hữu ích - mặc dù kém thông minh hơn đáng kể so với pkg_resources.parse_version()chức năng cấp cao hơn . Việc bạn nên tận dụng tùy thuộc vào mức độ điên rồ mà bạn mong đợi trong chuỗi phiên bản.
Cecil Curry,

@CecilCurry Có tất nhiên tôi đã đọc nhận xét (ary), đó là lý do tại sao tôi đã chỉnh sửa nó để làm cho nó tốt hơn, và sau đó nói rằng tôi đã có. Có lẽ bạn không đồng ý với tuyên bố của tôi setuptoolsnằm ngoài thư viện tiêu chuẩn, và thay vào đó là sở thích đã nêu của tôi distutils trong trường hợp này . Vì vậy, chính xác bạn có nghĩa là gì về "bắt buộc hiệu quả", và vui lòng bạn có thể cung cấp bằng chứng rằng nó "bắt buộc hiệu quả" cách đây 4,5 năm khi tôi viết nhận xét này?
Adam Spiers

12

Không cần phải lặp lại các bộ giá trị phiên bản. Toán tử so sánh được xây dựng trong danh sách và bộ giá trị đã hoạt động chính xác như bạn muốn. Bạn chỉ cần mở rộng danh sách phiên bản bằng không đến độ dài tương ứng. Với python 2.6, bạn có thể sử dụng izip_longest để đệm các trình tự.

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)

Với các phiên bản thấp hơn, cần phải có một số hack bản đồ.

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)

Tuyệt, nhưng khó hiểu đối với một người không thể đọc mã như văn xuôi. :) Chà, tôi cho rằng bạn chỉ có thể rút ngắn giải pháp với chi phí là khả năng đọc được ...
Johannes Charra

10

Điều này nhỏ gọn hơn một chút so với gợi ý của bạn. Thay vì điền các số không vào phiên bản ngắn hơn, tôi sẽ xóa các số không ở cuối khỏi danh sách phiên bản sau khi tách.

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

Tốt lắm, thx. Nhưng tôi vẫn hy vọng cho một hoặc hai-liner ...;)
Johannes Charra

4
+1 @jellybean: hai lớp lót không phải lúc nào cũng tốt nhất để bảo trì và dễ đọc, đây là lớp mã rất rõ ràng và nhỏ gọn đồng thời, ngoài ra, bạn có thể sử dụng lại mycmpcho các mục đích khác trong mã của mình nếu cần.
RedGlyph

@RedGlyph: Bạn đã có lý ở đó. Đáng lẽ phải nói "một chữ hai lót có thể đọc được". :)
Johannes Charra

chào @ Pär Wieslander, khi tôi sử dụng giải pháp này để giải quyết vấn đề tương tự ở vấn đề Leetcode, tôi gặp lỗi ở vòng lặp while cho biết "chỉ mục danh sách nằm ngoài phạm vi". Bạn có thể vui lòng giúp tại sao điều đó xảy ra? Đây là sự cố: leetcode.com/explore/interview/card/amazon/76/array-and-strings/…
YouHaveaBigEgo

7

Loại bỏ dấu vết .0.00với regex, splitvà sử dụng cmphàm so sánh các mảng một cách chính xác:

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)

Và, tất nhiên, bạn có thể chuyển đổi nó thành một lớp lót nếu bạn không bận tâm đến những dòng dài.


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

Đó là một lớp lót (tách ra để dễ đọc). Không chắc chắn về việc có thể đọc được ...


1
Đúng! Và thu nhỏ hơn nữa ( tuplekhông cần thiết 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

Triển khai cho php version_compare, ngoại trừ "=". Bởi vì nó mơ hồ.


2

Các danh sách có thể so sánh bằng Python, vì vậy nếu ai đó chuyển đổi các chuỗi đại diện cho các số thành số nguyên, thì phép so sánh cơ bản trong Python có thể được sử dụng thành công.

Tôi cần mở rộng cách tiếp cận này một chút vì tôi sử dụng Python3x nơi cmphàm không tồn tại nữa. Tôi đã phải thi đua cmp(a,b)với (a > b) - (a < b). Và, số phiên bản không rõ ràng chút nào và có thể chứa tất cả các loại ký tự chữ và số khác. Có những trường hợp khi hàm không thể cho biết thứ tự để nó trả về False(xem ví dụ đầu tiên).

Vì vậy, tôi đăng bài này ngay cả khi câu hỏi đã cũ và đã được trả lời, bởi vì nó có thể tiết kiệm vài phút trong cuộc đời của ai đó.

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

Trong trường hợp bạn không muốn sử dụng phụ thuộc bên ngoài, đây là nỗ lực của tôi được viết cho Python 3.x.

rc, rel(và có thể người ta có thể thêm vào c) được coi là "ứng cử viên phát hành" và chia số phiên bản thành hai phần và nếu thiếu giá trị của phần thứ hai là cao (999). Các chữ cái khác tạo ra sự phân tách và được xử lý dưới dạng số con thông qua mã cơ số 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

Tuy nhiên, giải pháp khó đọc nhất nhưng lại là một giải pháp duy nhất! và sử dụng trình vòng lặp để nhanh chóng.

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)

đó là dành cho Python2.6 và 3. + btw, Python 2.5 trở lên cần bắt StopIteration.


1

Tôi đã làm điều này để có thể phân tích cú pháp và so sánh chuỗi phiên bản gói Debian. Xin lưu ý rằng nó không nghiêm ngặt với việc xác nhận ký tự.

Điều này cũng có thể hữu ích:

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

Giải pháp khác:

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

Người ta cũng có thể sử dụng như thế này:

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

tôi đang sử dụng cái này trong dự án của mình:

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

0

Nhiều năm sau, nhưng câu hỏi này vẫn ở trên cùng.

Đây là chức năng sắp xếp phiên bản của tôi. Nó chia phiên bản thành các phần số và phần không phải số. Các con số được so sánh như intphần còn lại str(như một phần của các mục danh sách).

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

Bạn có thể sử dụng hàm keynhư kiểu tùy chỉnh Versionvới các toán tử so sánh. Nếu bạn thực sự muốn sử dụng, cmpbạn có thể làm như trong ví dụ này: 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

Bộ thử nghiệm vượt qua.


-1

Giải pháp ưa thích của tôi:

Việc đệm chuỗi bằng các số 0 thừa và chỉ sử dụng bốn đầu tiên là dễ hiểu, không yêu cầu bất kỳ regex nào và lambda ít nhiều có thể đọc được. Tôi sử dụng hai dòng để dễ đọc, đối với tôi, sự thanh lịch là ngắn gọn và đơn giản.

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

Đây là giải pháp của tôi (được viết bằng C, xin lỗi). Tôi hy vọng bạn sẽ thấy nó hữu ích

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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.