Python의 버전 번호 비교
내가 쓰고 싶은 cmp
두 가지 버전 번호 및 수익률을 비교 -like 기능을 -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
문자열에서 흥미롭지 않은 부분 (후행 0과 점)을 제거한 다음 숫자 목록을 비교합니다.
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와 동일한 접근 방식이지만 좀 더 간결합니다.
이 게시물 덕분에 몇 가지 테스트 :
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
파이썬을 사용하는 것은 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
이 품종에서 성공하려면 표준 라이브러리에서 벗어나 setuptools 의 구문 분석 유틸리티를 사용해야 합니다 parse_version
.
>>> from pkg_resources import parse_version
>>> parse_version('1.4') > parse_version('1.4-rc2')
True
따라서 특정 사용 사례에 따라 기본 제공 distutils
도구가 충분한 지 또는 종속성으로 추가 해야하는지 여부를 결정해야합니다 setuptools
.
이 경우 재사용 은 우아함으로 간주됩니까? :)
# 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))
버전 튜플을 반복 할 필요가 없습니다. 목록과 튜플에 내장 된 비교 연산자는 이미 원하는대로 작동합니다. 버전 목록을 해당 길이로 0으로 확장하면됩니다. 파이썬 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)
이것은 당신의 제안보다 조금 더 간결합니다. 짧은 버전을 0으로 채우는 대신 분할 후 버전 목록에서 후행 0을 제거합니다.
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))
regex로 후행 .0 및 .00을 제거하고 배열을 올바르게 비교하는 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)
물론 긴 줄이 마음에 들지 않으면 한 줄로 변환 할 수 있습니다.
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('.')]))))
하나의 라이너입니다 (가독성을 위해 분할). 읽을 수 있는지 잘 모르겠습니다 ...
목록은 파이썬에서 비교할 수 있으므로 숫자를 나타내는 문자열을 정수로 변환하면 기본적인 파이썬 비교를 성공적으로 사용할 수 있습니다.
그러나 나는이 접근 방식을 약간 확장해야했습니다. 먼저 cmp 함수가 더 이상 존재하지 않는 python3x를 사용하기 때문에 (a> b)-(a <b) 와 함께 cmp (a, b) 를 에뮬레이트해야했습니다 .
둘째, 슬프게도 버전 번호는 전혀 깨끗하지 않으며 모든 종류의 다른 영숫자 문자를 포함 할 수 있습니다. 함수가 순서를 알 수 없어 return이 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'))
외부 종속성을 가져오고 싶지 않은 경우 여기에 내 시도가 있습니다 (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') >> 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') >> 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') >> compare_version('1.1', '1.10') >> 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') >> 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))
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
version_compare
"="를 제외하고 php 용으로 구현하십시오 . 모호하기 때문에.
가장 읽기 어려운 솔루션이지만 그럼에도 불구하고 한 줄짜리입니다! 반복자를 사용하여 빠르게합니다.
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을 포착해야합니다.
또 다른 해결책 :
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)
데비안 패키지 버전 문자열을 구문 분석하고 비교할 수 있도록이 작업을 수행했습니다. 캐릭터 유효성 검사에 엄격하지 않습니다.
이것은 또한 도움이 될 수 있습니다.
#!/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')
내 프로젝트에서 이것을 사용하고 있습니다.
cmp(v1.split("."), v2.split(".")) >= 0
내가 선호하는 솔루션 :
추가 0으로 문자열을 채우고 첫 번째 4 개만 사용하는 것은 이해하기 쉽고 정규식이 필요하지 않으며 람다는 어느 정도 읽기 쉽습니다. 가독성을 위해 두 줄을 사용합니다. 우아함은 짧고 단순합니다.
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))
이것은 내 솔루션입니다 (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;
}
참고 URL : https://stackoverflow.com/questions/1714027/version-number-comparison-in-python
'developer tip' 카테고리의 다른 글
클래스 이름으로 요소를 제거 하시겠습니까? (0) | 2020.08.29 |
---|---|
in_array 여러 값 (0) | 2020.08.29 |
중복 항목을 삭제하는 방법은 무엇입니까? (0) | 2020.08.29 |
디렉터리에 폴더가 있는지 확인하고 C #을 사용하여 만듭니다. (0) | 2020.08.29 |
UITabBarItem 이미지를 아래로 이동 하시겠습니까? (0) | 2020.08.29 |