3

I have an array with strings comprising of numbers in a dotted notation as such:

var arr = ['1.2.5.4', '1.2.5.3'];

I would like to be able to find the string that denotes the highest value.

I already tried

Math.max.apply(null, arr)

Since there are multiple decimals, it simply won't work.

Michael Myers
  • 188,989
  • 46
  • 291
  • 292
Louis
  • 375
  • 1
  • 5
  • 12

4 Answers4

2

You'll need to create your own sorting subroutine for this, since they're to be treated as version numbers. You'll want to do a left-first comparison, comparing each number delimited by periods.

Here's a good bit of code stolen from another answer that does exactly as I mentioned:

function assert(x) {
    if (!x) {
        alert("Assert failed");
        debugger;
    }
}

function isPositiveInteger(x) {
    // https://stackoverflow.com/a/1019526/11236
    return /^\d+$/.test(x);
}

/**
 * Compare two software version numbers (e.g. 1.7.1)
 * Returns:
 *
 *  0 if they're identical
 *  negative if v1 < v2
 *  positive if v1 > v2
 *  Nan if they in the wrong format
 *
 *  E.g.:
 *
 *  assert(version_number_compare("1.7.1", "1.6.10") > 0);
 *  assert(version_number_compare("1.7.1", "1.7.10") < 0);
 *
 *  "Unit tests": http://jsfiddle.net/ripper234/Xv9WL/28/
 *
 *  Taken from https://stackoverflow.com/a/6832721/11236
 */
function compareVersionNumbers(v1, v2){
    var v1parts = v1.split('.');
    var v2parts = v2.split('.');

    // First, validate both numbers are true version numbers
    function validateParts(parts) {
        for (var i = 0; i < parts.length; ++i) {
            if (!isPositiveInteger(parts[i])) {
                return false;
            }
        }
        return true;
    }
    if (!validateParts(v1parts) || !validateParts(v2parts)) {
        return NaN;
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length === i) {
            return 1;
        }

        if (v1parts[i] === v2parts[i]) {
            continue;
        }
        if (v1parts[i] > v2parts[i]) {
            return 1;
        }
        return -1;
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}


assert(compareVersionNumbers("1.7.1", "1.7.10") < 0);
assert(compareVersionNumbers("1.6.1", "1.7.10") < 0);
assert(compareVersionNumbers("1.6.20", "1.7.10") < 0);
assert(compareVersionNumbers("1.7.1", "1.7.10") < 0);
assert(compareVersionNumbers("1.7", "1.7.0") < 0);
assert(compareVersionNumbers("1.7", "1.8.0") < 0);

assert(compareVersionNumbers("1.7.10", "1.7.1") > 0);
assert(compareVersionNumbers("1.7.10", "1.6.1") > 0);
assert(compareVersionNumbers("1.7.10", "1.6.20") > 0);
assert(compareVersionNumbers("1.7.0", "1.7") > 0);
assert(compareVersionNumbers("1.8.0", "1.7") > 0);

assert(compareVersionNumbers("1.7.10", "1.7.10") === 0);
assert(compareVersionNumbers("1.7", "1.7") === 0);

assert(isNaN(compareVersionNumbers("1.7", "1..7")));
assert(isNaN(compareVersionNumbers("1.7", "Bad")));
assert(isNaN(compareVersionNumbers("1..7", "1.7")));
assert(isNaN(compareVersionNumbers("Bad", "1.7")));

alert("All done");
​
Community
  • 1
  • 1
Jonathan M
  • 17,145
  • 9
  • 58
  • 91
  • WARNING: This appears to give the incorrect answer for certain minor versions: `compareVersionNumbers("1.7.10", "1.66.10")` produces `1` – Max Strater Jul 30 '21 at 22:44
2

This seems a simpler solution:

function latest (versions) {
  versions.sort (function (a, b) {
    a = a.split ('.');
    b = b.split ('.');   
    for (var i = 0; i < a.length && i < b.length && a[i] === b[i]; i++);
    return ((i === a.length) || (+a[i] < +b[i])) ? 1 : -1;
  });
  return versions[0]; 
}

[
  latest (['1.2.5.4', '1.3.5.3', '1.2.3.4.5', '1.24.2.1', '1.2.52']),
  latest (['1.2.5.4', '1.3.5.3', '1.2.3.4.5', '1.2.52']),
  latest (['1.2.5.4', '1.2.3.4.5', '1.2.52']),
  latest (['1.2.5.4', '1.2.3.4.5'])
]

/* Displays on JS console

    ["1.24.2.1", "1.3.5.3", "1.2.52", "1.2.5.4"]
*/

Thanks for the vote, on review I noted a bug which I have corrected in the code above.

HBP
  • 15,685
  • 6
  • 28
  • 34
  • Warning : this function sorts the original array ! You should use de `reduce` function instead, to "reduce the array to a single value, aka the latest version" instead of sorting it and taking the first item. – M'sieur Toph' Mar 09 '15 at 09:18
0

Here's a human sort implementation:

Array.prototype.humanSort = function() {
  return this.sort(function(a, b) {
    aa = a.split('.');
    bb = b.split('.');

    for(var x = 0; x < Math.max(aa.length, bb.length); x++) {
      if(aa[x] != bb[x]) {
        var cmp1 = (isNaN(parseInt(aa[x],10)))? aa[x] : parseInt(aa[x],10);
        var cmp2 = (isNaN(parseInt(bb[x],10)))? bb[x] : parseInt(bb[x],10);
        if(cmp1 == undefined || cmp2 == undefined)
          return aa.length - bb.length;
        else
          return (cmp1 < cmp2) ? -1 : 1;
      }
    }
    return 0;
  });
}

Call it like this:

 ['1.12.5.4', '1.2.5.3'].humanSort(); // => ['1.2.5.3', '1.12.5.4']
Kvam
  • 2,148
  • 1
  • 20
  • 32
  • Have you tested the above code for all the scenarios mentioned in the comments as a minimum? – bPratik May 15 '12 at 19:58
  • 1.12.5.4 Would have to be greater then 1.2.5.3. Since 12 > 2. – Louis May 15 '12 at 20:01
  • 1
    I tested it for all the scenarios mentioned in the comments. The sort algorithm provided sorts the elements in ascending order, meaning the last element in the sorted array is the largest. – Kvam May 15 '12 at 21:42
0

Here is a 'better' solution on my opinion (because the checked one modifies the original array, and this is evil ;) ) :

function latest(versions) {
    return versions.reduce(function(latest, current){
        var l = latest.split('.'),
            c = current.split('.');
        for (var i=0,len=Math.min(l.length, c.length); i<len; i++){
            if (+l[i] === +c[i]) {
                continue;
            } else {
                return +l[i] < +c[i] ? current : latest;
            }
        }
        return l.length < c.length ? current : latest;
    }, "0");
}

The results are exactly the same than the checked answer, except the original array is still pristine.

[
  latest (['1.2.5.4', '1.3.5.3', '1.2.3.4.5', '1.24.2.1', '1.2.52']),
  latest (['1.2.5.4', '1.3.5.3', '1.2.3.4.5', '1.2.52']),
  latest (['1.2.5.4', '1.2.3.4.5', '1.2.52']),
  latest (['1.2.5.4', '1.2.3.4.5'])
]

/* Displays on JS console

    ["1.24.2.1", "1.3.5.3", "1.2.52", "1.2.5.4"]
*/

And it takes advantage from the reduce function, which is exactly made for this usage.

M'sieur Toph'
  • 2,534
  • 1
  • 21
  • 34