2

The answers to this question have helped me to compare two version number strings and see which version is "greater", i.e. newer.

What I now need to do is to calculate the actual difference between two version numbers. Mostly to see if a new major version has been released, or only a minor version.

"1.3.6" - "1.3.3" should return "0.0.3"

"5.2.0" - "4.0.0" should return "1.2.0"

I could write a function that calculates the difference myself (easy in theory), but it would be a hassle to include all the cases pkg_resources already considered, like letters between or at the end of version numbers.

I've looked into the documentary of pkg_resources, but simple subtraction doesn't seem to work. Are there any other already implemented solutions for this problem?

Edit: Okay, simple subtraction doesn't make much sense, now that I think about it. It will dilute the borders between major and minor versions (e.g. "2.1" - "1.2" = "0.9" which isn't helpful at all). (Thanks @Jeremy Banks)

Community
  • 1
  • 1
R.G.
  • 831
  • 1
  • 8
  • 16
  • 5
    What does 2.1 - 1.2 equal? – Jeremy Dec 18 '15 at 09:23
  • 1
    Ah, right. Simple subtraction doesn't make much sense, then. Didn't think it over properly. "1.-1" would be a strange number. – R.G. Dec 18 '15 at 09:26
  • Are you doing this for a specific package/application or for many applications? If it is app-specific, then you should look at the version history for that app. Usually developers increment the third number for minor releases, second for major-ish releases like adding a small feature, UI changes, etc. And the first number for *big* changes like adding a very important feature. But it depends from dev to dev. – EvilTak Dec 18 '15 at 09:29
  • @EvilTak Right now I'm only doing this for a specific type numbering, but I would like to create a flexible solution if possible. But you're right that the way developers handle version numbers are not uniform. – R.G. Dec 18 '15 at 09:32
  • 2
    Even when underflow doesn't occur, `"5.2.0" - "4.0.0" should return "1.2.0"` doesn't make sense--the difference between 4.0 and 5.2 is basically "one major version" because there may never have been a 4.x after 4.0 (for example). – John Zwinck Dec 18 '15 at 09:32
  • @JohnZwinck Yes, you're definitely right! I need to compare the version fragments separately. – R.G. Dec 18 '15 at 09:33

4 Answers4

2

Here's a function I just wrote that does something along these lines:

def diffver(v1, v2):
    v1s = map(int, v1.split('.'))
    v2s = map(int, v2.split('.'))

    for ii, (v1r, v2r) in enumerate(zip(v1s, v2s), 1):
        if v1r != v2r:
            return ii, v1r - v2r

    return ii, 0

print diffver("4.0.0", "5.2.0")
print diffver("5.1.0", "5.2.0")
print diffver("5.4.0", "5.2.0")
print diffver("5.4.0", "5.4.0")

It prints:

(1, -1)
(2, -1)
(2, 2)
(3, 0)

The idea is to return a tuple (PART, DIFF) where PART is 1 for major, 2 for minor, etc., and DIFF being how different that part is. No difference gives you PART being how many parts were compared.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

You can compare every parts one by one:

def compare(a, b):
    a_parts = a.split('.')
    b_parts = b.split('.')
    v = ['major', 'minor', 'build']
    for i in range(3):
        diff = int(a_parts[i]) - int(b_parts[i])
        if diff is 0:
            continue
        if diff > 0:
            direction = 'ahead'
        else:
            direction = 'behind'
            diff = -diff

        return 'pkg is %s %s version %s' %  (diff, v[i], direction)
    return 'version are equal'

print compare('3.2.1', '2.0.0') # pkg is 1 major version ahead
print compare('3.2.1', '3.0.0') # pkg is 2 minor version ahead
print compare('3.2.1', '3.2.0') # pkg is 1 build version ahead
print compare('3.2.1', '3.2.1') # version are equal
print compare('3.2.1', '6.2.1') # pkg is 3 major version behind

If your version number is not standard (ie: 1.2b.4321) you can preparse with tool like parse_version.

Cyrbil
  • 6,341
  • 1
  • 24
  • 40
0

Okay, thanks for your comments so far, I see now that it doesn't make sense to compare version numbers as a whole.

It only makes sense to compare the "fragments" of the whole version number, i.e. compare only the first or second parts. Which is what I will probably do.

More specifically: I will first see if there is a difference in the major version (first part). If this is the case, minor version don't matter. If this is not the case, I will go on and compare the next part of the version number.

This solution will still make it bothersome to include cases in which version numbers include letters (like "1.8.4b"), but I guess there is no way around it.

R.G.
  • 831
  • 1
  • 8
  • 16
0

Maybe a bit more compact:

def compare_versions(ver1, ver2):
    numbers1 = [int(x) for x in ver1.split('.')]
    numbers2 = [int(x) for x in ver2.split('.')]
    return '.'.join(str(v1 - v2)  for v1, v2 in zip(numbers1, numbers2))

>>> compare_versions("1.3.6", "1.3.3")
'0.0.3'    
>>> compare_versions("5.2.0", "4.0.0" )
'1.2.0'

A variation that is one line shorter:

def compare_versions(ver1, ver2):
    split_dot = lambda ver: [int(x) for x in ver.split('.')]
    return  '.'.join(str(v1 - v2)  for v1, v2 in zip(split_dot(ver1), split_dot(ver2)))
Mike Müller
  • 82,630
  • 20
  • 166
  • 161