Zehouani Alae
Zehouani Alae

Reputation: 101

Sort list of version numbers groovy

I have a list of version numbers like,

Versions = [0.0.10, 0.0.11, 0.0.13, 0.0.14, 0.0.15, 0.0.16, 0.0.17, 0.0.18, 0.0.19, 0.0.20, 0.0.21, 0.0.22, 0.0.23, 0.0.24, 0.0.25, 0.0.26, 0.0.27, 0.0.28, 0.0.29, 0.0.3, 0.0.30, 0.0.33, 0.0.34, 0.0.35, 0.0.36, 0.0.37, 0.0.38, 0.0.39, 0.0.4, 0.0.41, 0.0.42, 0.0.43, 0.0.44, 0.0.45, 0.0.46, 0.0.47, 0.0.48, 0.0.49, 0.0.5, 0.0.5-delivery.5, 0.0.50, 0.0.51, 0.0.52, 0.0.53, 0.0.54, 0.0.55, 0.0.56, 0.0.57, 0.0.58, 0.0.59, 0.0.6, 0.0.60, 0.0.61, 0.0.62, 0.0.63, 0.0.64, 0.0.7, 0.0.8, 0.0.9]'

And i need to get the last version (0.0.64), Versions.sort() && Collections.max(Versions) doesn't work for me.

So I developed this function blow

def mostRecentVersion(def versions) {
def  lastversion = "0.0.0"
for (def items : versions) {

    def version = items.tokenize('-')[0]
    def ver = version.tokenize('.')
    def lastver = lastversion.tokenize('.')

    if (lastver[0].toInteger() < ver[0].toInteger() ){
        lastversion = version

    }else if(lastver[0].toInteger() == ver[0].toInteger()) {

        if (lastver[1].toInteger() < ver[1].toInteger() ){
            lastversion = version

        }else if(lastver[1].toInteger() == ver[1].toInteger()){

            if (lastver[2].toInteger() < ver[2].toInteger() ){
                lastversion = version

            }
        }
    }
}
return lastversion }

i'm asking if there is something better,

Thank you for help :)

Upvotes: 1

Views: 3472

Answers (4)

neoscribe
neoscribe

Reputation: 2421

I needed to choose release candidates from a dropdown in Jenkins' Active Choices Parameter, but the upvoted answers failed with CPS error "spread not yet supported for CPS transformation" or simply didn't work in Jenkins' Groovy sandbox, it's VERY picky.

Here's magic that worked for me:

    def fourPartVersion(v){ 
      x = "${v}.0.0.0".tokenize(".")
      return "${x[0].padLeft(20,'0')}${x[1].padLeft(20,'0')}${x[2].padLeft(20,'0')}${x[3].padLeft(20,'0')}" 
    }

    // Returns sorted released candidates including semantic version pre-release and "1-3dot" version numbers from a list of version strings. 
    // e.g.  filterCandidates(["1.0.0.4567", "notaversion", "12.0.324", "release-1.23.0", "12.0.324-rc1", "12.0.324-rc2", "2.0.324-rc1", "2.0","3.0","2.0.1"])  => ["1.0.0.4567", "2.0", "2.0.324-rc1", "12.0.324", "12.0.324-rc1", "12.0.324-rc2"]
    // @param versions - list of raw strings
    def filterCandidates(versions) {
      def fmtversions = []
      def sortversions = []
      versions?.each { v->
            if (v ==~ /^(\d+)\.(\d+)(?!([-]|\.[a-zA-Z-]))\.?(\d+)?(?!([-]\d+|[\.][a-zA-Z]))[\.-]?(\d+|[a-zA-Z][0-9a-zA-Z]*)?$/) {
                  fmtversions.add("${this.fourPartVersion(v)}:${v}")
            }
      }
      fmtversions.sort().each{ sortversions.add(it.tokenize(':').last())}
      return sortversions
  }

The above code converts each potential version string to a four part version and pads each part so that strings sort like numbers. The regex ensures only well formatted version strings are added to the output before sorting.

If you only want "released" versions you use the following regex instead of the one on line 12:

    /^(0|[1-9]\d*)\.(0|[1-9]\d*)(\.\d*|)(\.\d*|)?$/

Upvotes: 0

anessi
anessi

Reputation: 106

Here is an updated answer based on the Java Version class (>= JDK 9).

import java.lang.module.ModuleDescriptor

def sortVersions(ArrayList<String> versions) {
    return versions.sort { v1, v2 -> {
        ModuleDescriptor.Version.parse(v1).compareTo(ModuleDescriptor.Version.parse(v2))
    }}
}

def mostRecentVersion(ArrayList<String> versions){
    return sortVersions(versions)[-1]
}

//test cases:
def fullVersions = ['0.0.10', '0.0.11', '0.0.13', '0.0.14', '0.0.15', '0.0.16',
                    '0.0.17', '0.0.18', '0.0.19', '0.0.20', '0.0.21', '0.0.22', '0.0.23', '0.0.24',
                    '0.0.25', '0.0.26', '0.0.27', '0.0.28', '0.0.29', '0.0.3', '0.0.30', '0.0.33',
                    '0.0.34', '0.0.35', '0.0.36', '0.0.37', '0.0.38', '0.0.39', '0.0.4', '0.0.41',
                    '0.0.42', '0.0.43', '0.0.44', '0.0.45', '0.0.46', '0.0.47', '0.0.48', '0.0.49',
                    '0.0.5', '0.0.5-delivery.5', '0.0.50', '0.0.51', '0.0.52', '0.0.53', '0.0.54',
                    '0.0.55', '0.0.56', '0.0.57', '0.0.58', '0.0.59', '0.0.6', '0.0.60', '0.0.61',
                    '0.0.62', '0.0.63', '0.0.64', '0.0.7', '0.0.8', '0.0.9']

assert mostRecentVersion(fullVersions) == '0.0.64'

// this results in 0.0.5, not 0.0.5-delivery.5 as in the answers above
assert mostRecentVersion(['0.0.5-delivery.5', '0.0.3', '0.0.5']) == '0.0.5'

assert mostRecentVersion(['0.0.5.5', '0.0.5-delivery.5', '0.0.5']) == '0.0.5.5'

Note that the Java module sorts differently than the answers above, so that0.0.5-delivery.5 < 0.0.5

Also see https://stackoverflow.com/a/74150491/16308242

Upvotes: 1

daggett
daggett

Reputation: 28599

the idea:

build map with sortable key and original version value, then sort map by keys, then get only values

to create sortable key for each value

  1. split version to digits & not-digit strings array
  2. prepend to each part 0 to have minimum length 3 (assume each number not longer then 3 digits)
  3. join array to string

so, for 0.11.222-dev ->

1. [   '0',   '.',  '11', '222', '-dev' ]
2. [ '000', '00.', '011', '222', '-dev' ]
3. '00000.011222-dev'

the code

def mostRecentVersion(versions){
    return versions.collectEntries{ 
        [(it=~/\d+|\D+/).findAll().collect{it.padLeft(3,'0')}.join(),it]
    }.sort().values()[-1]
}
//test cases:
def fullVersions = ['0.0.10', '0.0.11', '0.0.13', '0.0.14', '0.0.15', '0.0.16', 
'0.0.17', '0.0.18', '0.0.19', '0.0.20', '0.0.21', '0.0.22', '0.0.23', '0.0.24', 
'0.0.25', '0.0.26', '0.0.27', '0.0.28', '0.0.29', '0.0.3', '0.0.30', '0.0.33', 
'0.0.34', '0.0.35', '0.0.36', '0.0.37', '0.0.38', '0.0.39', '0.0.4', '0.0.41', 
'0.0.42', '0.0.43', '0.0.44', '0.0.45', '0.0.46', '0.0.47', '0.0.48', '0.0.49', 
'0.0.5', '0.0.5-delivery.5', '0.0.50', '0.0.51', '0.0.52', '0.0.53', '0.0.54', 
'0.0.55', '0.0.56', '0.0.57', '0.0.58', '0.0.59', '0.0.6', '0.0.60', '0.0.61', 
'0.0.62', '0.0.63', '0.0.64', '0.0.7', '0.0.8', '0.0.9']

assert mostRecentVersion(fullVersions) == '0.0.64'

assert mostRecentVersion(['0.0.5-delivery.5', '0.0.3', '0.0.5']) == '0.0.5-delivery.5'

assert mostRecentVersion(['0.0.5.5', '0.0.5-delivery.5', '0.0.5']) == '0.0.5.5'

Upvotes: 5

tim_yates
tim_yates

Reputation: 171144

I believe this will work... it also keeps the original version strings around, incase 0.5.5-devel.5 is the latest... It relies on the fact that Groovy will use a LinkedHashMap for the sorted map, so the order will be preserved :-)

def mostRecentVersion(def versions) {
    versions.collectEntries {
        [it, it.split(/\./).collect { (it =~ /([0-9]+).*/)[0][1] }*.toInteger()]
    }.sort { a, b ->
        [a.value, b.value].transpose().findResult { x, y -> x <=> y ?: null } ?:
            a.value.size() <=> b.value.size() ?:
            a.key <=> b.key
    }.keySet()[-1]
}

def fullVersions = ['0.0.10', '0.0.11', '0.0.13', '0.0.14', '0.0.15', '0.0.16', '0.0.17', '0.0.18', '0.0.19', '0.0.20', '0.0.21', '0.0.22', '0.0.23', '0.0.24', '0.0.25', '0.0.26', '0.0.27', '0.0.28', '0.0.29', '0.0.3', '0.0.30', '0.0.33', '0.0.34', '0.0.35', '0.0.36', '0.0.37', '0.0.38', '0.0.39', '0.0.4', '0.0.41', '0.0.42', '0.0.43', '0.0.44', '0.0.45', '0.0.46', '0.0.47', '0.0.48', '0.0.49', '0.0.5', '0.0.5-delivery.5', '0.0.50', '0.0.51', '0.0.52', '0.0.53', '0.0.54', '0.0.55', '0.0.56', '0.0.57', '0.0.58', '0.0.59', '0.0.6', '0.0.60', '0.0.61', '0.0.62', '0.0.63', '0.0.64', '0.0.7', '0.0.8', '0.0.9']

assert mostRecentVersion(fullVersions) == '0.0.64'

assert mostRecentVersion(['0.0.5-delivery.5', '0.0.3', '0.0.5']) == '0.0.5-delivery.5'

assert mostRecentVersion(['0.0.5.5', '0.0.5-delivery.5', '0.0.5']) == '0.0.5.5'

Edit:

Made a change so that 0.5.5.5 > 0.5.5-devel.5

Upvotes: 4

Related Questions