Related
I have an Array that I need to sort exactly like using order by in Oracle SQl.
If I have following Array:
var array = ['Ba12nes','Apfel','Banane','banane','abc','ABC','123','2', null,'ba998ne']
var.sort(compare);
I would like to have the following result
var array = ['abc','ABC','Apfel','banane','Banane','Ba12nes','ba998ne','123','2', null]
If the null values are somewhere else, I don't have a Problem with it.
My current solution, which does not help me ^^
function compare(a,b) {
if(a == null)
return -1;
if (b == null)
return 1;
if (a.toLowerCase() < b.toLowerCase())
return -1;
if (a.toLowerCase() > b.toLowerCase())
return 1;
return 0;
}
I do understand that i need a custom sorting function. And at the moment I am thinking that only a regular expression can solve the problem of sorting the string values in front of the numbers. But I am still not sure how to solve the Problem with lowercase letters in bevor Uppercase letters.
Iirc, Oracle implements a 3-tiered lexicographic sorting (but heed the advice of Alex Poole and check the NLS settings first):
First sort by base characters ignoring case and diacritics, digits come after letters in the collation sequence.
Second, on ties sort respecting diacritics, ignoring case.
Third, on ties sort by case.
You can emulate the behavior using javascript locale apis by mimicking each step in turn in a custom compare function, with the exception of the letter-digit inversion in the collation sequence.
Tackle the latter by identifying 10 contiguous code points that do not represent digits and that lie beyond the set of code points that may occur in the strings you are sorting. Map digits onto the the chosen code point range preserving order. When you sort, specify the Unicode collating extension 'direct' which means 'sorting by code point'. Remap after sorting.
In the PoC code below I have chosen some cyrillic characters.
function cmptiered(a,b) {
//
// aka oracle sort
//
return lc_base.compare(a, b) || lc_accent.compare(a, b) || lc_case.compare(a, b);
} // cmptiered
var lc_accent = new Intl.Collator('de', { sensitivity: 'accent' });
var lc_base = new Intl.Collator('de-DE-u-co-direct', { sensitivity: 'base' });
var lc_case = new Intl.Collator('de', { caseFirst: 'lower', sensitivity: 'variant' });
var array = ['Ba12nes','Apfel','Banane','banane','abc','ABC','123','2', null, 'ba998ne' ];
// Map onto substitute code blocks
array = array.map ( function ( item ) { return (item === null) ? null : item.replace ( /[0-9]/g, function (c) { return String.fromCharCode(c.charCodeAt(0) - "0".charCodeAt(0) + "\u0430".charCodeAt(0)); } ); } );
array.sort(cmptiered);
// Remap substitute code point
array = array.map ( function ( item ) { return (item === null) ? null : item.replace ( /[\u0430-\u0439]/g, function (c) { return String.fromCharCode(c.charCodeAt(0) - "\u0430".charCodeAt(0) + "0".charCodeAt(0)); } ); } );
Edit
Function cmptiered streamlined following Nina Scholz' comment.
This proposals feature sorting without use of Intl.Collator. The first solution works with direct sort and comparing the given values.
var array = ['Ba12nes', 'Apfel', 'Banane', 'banane', 'abc', 'ABC', '123', '2', null, 'ba998ne'];
array.sort(function (a, b) {
var i = 0;
if (a === null && b === null) { return 0; }
if (a === null) { return 1; }
if (b === null) { return -1; }
while (i < a.length && i < b.length && a[i].toLocaleLowerCase() === b[i].toLocaleLowerCase()) {
i++;
}
if (isFinite(a[i]) && isFinite(b[i])) { return a[i] - b[i]; }
if (isFinite(a[i])) { return 1; }
if (isFinite(b[i])) { return -1; }
return a.localeCompare(b);
});
document.write(JSON.stringify(array));
The second solution features a different approach, based on Sorting with map and a custom sort scheme which takes a new string. The string is build by this rules:
If the value is null take the string 'null'.
If a character is a decimal, takes the character with space paddded around, eg. if it is 9 take the string ' 9 '.
Otherwise for every other character take two spaces and the character itself, like ' o'.
The new build string is used with a a.value.localeCompare(b.value).
Here are the strings with the mapped values:
' B a 1 2 n e s'
' A p f e l'
' B a n a n e'
' b a n a n e'
' a b c'
' A B C'
' 1 2 3 '
' 2 '
'null'
' b a 9 9 8 n e'
sorted, it became
' a b c'
' A B C'
' A p f e l'
' b a n a n e'
' B a n a n e'
' B a 1 2 n e s'
' b a 9 9 8 n e'
' 1 2 3 '
' 2 '
'null'
var array = ['Ba12nes', 'Apfel', 'Banane', 'banane', 'abc', 'ABC', '123', '2', null, 'ba998ne'],
mapped = array.map(function (el, i) {
var j, o = { index: i, value: '' };
if (el === null) {
o.value = 'null';
return o;
}
for (j = 0; j < el.length; j++) {
o.value += /\d/.test(el[j]) ? ' ' + el[j] + ' ' : ' ' + el[j];
}
return o;
});
mapped.sort(function (a, b) {
return a.value.localeCompare(b.value);
});
var result = mapped.map(function (el) {
return array[el.index];
});
document.write(JSON.stringify(result));
A simple head on solution that works at least for english & russian (mimicking NLS_SORT=RUSSIAN) and doesn't rely on fancy things like Intl.Collator, locales and options that don't exist for IE<11.
function compareStringOracle(str1, str2) {
if (str1 == null && str2 != null)
return 1;
else if (str1 != null && str2 == null)
return -1;
else if (str1 == null && str2 == null)
return 0;
else {
return compareStringCaseInsensitiveDigitsLast(str1, str2) ||
/* upper case wins between otherwise equal values, which can be checked with
a simple binary comparison (at least for eng & rus) */
((str1 < str2) ? -1 : (str1 > str2) ? 1 : 0);
}
}
function compareStringCaseInsensitiveDigitsLast(str1, str2) {
for (var i = 0; i < str1.length; ++i) {
if (i === str2.length)
return 1;
// toLocaleLowerCase is unnecessary for eng & rus
var c1 = str1.charAt(i).toLowerCase();
var c2 = str2.charAt(i).toLowerCase();
var d1 = "0" <= c1 && c1 <= "9";
var d2 = "0" <= c2 && c2 <= "9";
if (!d1 && d2)
return -1;
else if (d1 && !d2)
return 1;
else if (c1 !== c2)
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
if (str1.length < str2.length)
return -1;
else
return 0;
}
Here is the software version number:
"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"
How can I compare this?
Assume the correct order is:
"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"
The idea is simple...:
Read the first digit, than, the second, after that the third...
But I can't convert the version number to float number...
You also can see the version number like this:
"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"
And this is clearer to see what is the idea behind...
But, how can I convert it into a computer program?
semver
The semantic version parser used by npm.
$ npm install semver
var semver = require('semver');
semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')
Semantic Versioning Link : https://www.npmjs.com/package/semver#prerelease-identifiers
The basic idea to make this comparison would be to use Array.split to get arrays of parts from the input strings and then compare pairs of parts from the two arrays; if the parts are not equal we know which version is smaller.
There are a few of important details to keep in mind:
How should the parts in each pair be compared? The question wants to compare numerically, but what if we have version strings that are not made up of just digits (e.g. "1.0a")?
What should happen if one version string has more parts than the other? Most likely "1.0" should be considered less than "1.0.1", but what about "1.0.0"?
Here's the code for an implementation that you can use directly (gist with documentation):
function versionCompare(v1, v2, options) {
var lexicographical = options && options.lexicographical,
zeroExtend = options && options.zeroExtend,
v1parts = v1.split('.'),
v2parts = v2.split('.');
function isValidPart(x) {
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
}
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
return NaN;
}
if (zeroExtend) {
while (v1parts.length < v2parts.length) v1parts.push("0");
while (v2parts.length < v1parts.length) v2parts.push("0");
}
if (!lexicographical) {
v1parts = v1parts.map(Number);
v2parts = v2parts.map(Number);
}
for (var i = 0; i < v1parts.length; ++i) {
if (v2parts.length == i) {
return 1;
}
if (v1parts[i] == v2parts[i]) {
continue;
}
else if (v1parts[i] > v2parts[i]) {
return 1;
}
else {
return -1;
}
}
if (v1parts.length != v2parts.length) {
return -1;
}
return 0;
}
This version compares parts naturally, does not accept character suffixes and considers "1.7" to be smaller than "1.7.0". The comparison mode can be changed to lexicographical and shorter version strings can be automatically zero-padded using the optional third argument.
There is a JSFiddle that runs "unit tests" here; it is a slightly expanded version of ripper234's work (thank you).
Important note: This code uses Array.map and Array.every, which means that it will not run in IE versions earlier than 9. If you need to support those you will have to provide polyfills for the missing methods.
The simplest is to use localeCompare :
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
This will return:
0: version strings are equal
1: version a is greater than b
-1: version b is greater than a
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
if (a === b) {
return 0;
}
var a_components = a.split(".");
var b_components = b.split(".");
var len = Math.min(a_components.length, b_components.length);
// loop while the components are equal
for (var i = 0; i < len; i++) {
// A bigger than B
if (parseInt(a_components[i]) > parseInt(b_components[i])) {
return 1;
}
// B bigger than A
if (parseInt(a_components[i]) < parseInt(b_components[i])) {
return -1;
}
}
// If one's a prefix of the other, the longer one is greater.
if (a_components.length > b_components.length) {
return 1;
}
if (a_components.length < b_components.length) {
return -1;
}
// Otherwise they are the same.
return 0;
}
console.log(compare("1", "2"));
console.log(compare("2", "1"));
console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));
This very small, yet very fast compare function takes version numbers of any length and any number size per segment.
Return values:
- a number < 0 if a < b
- a number > 0 if a > b
- 0 if a = b
So you can use it as compare function for Array.sort();
EDIT: Bugfixed Version stripping trailing zeros to recognize "1" and "1.0.0" as equal
function cmpVersions (a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, '').split('.');
var segmentsB = b.replace(regExStrip0, '').split('.');
var l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}
}
return segmentsA.length - segmentsB.length;
}
// TEST
console.log(
['2.5.10.4159',
'1.0.0',
'0.5',
'0.4.1',
'1',
'1.1',
'0.0.0',
'2.5.0',
'2',
'0.0',
'2.5.10',
'10.5',
'1.25.4',
'1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]
Simple and short function:
function isNewerVersion (oldVer, newVer) {
const oldParts = oldVer.split('.')
const newParts = newVer.split('.')
for (var i = 0; i < newParts.length; i++) {
const a = ~~newParts[i] // parse int
const b = ~~oldParts[i] // parse int
if (a > b) return true
if (a < b) return false
}
return false
}
Tests:
isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false
Taken from http://java.com/js/deployJava.js:
// return true if 'installed' (considered as a JRE version string) is
// greater than or equal to 'required' (again, a JRE version string).
compareVersions: function (installed, required) {
var a = installed.split('.');
var b = required.split('.');
for (var i = 0; i < a.length; ++i) {
a[i] = Number(a[i]);
}
for (var i = 0; i < b.length; ++i) {
b[i] = Number(b[i]);
}
if (a.length == 2) {
a[2] = 0;
}
if (a[0] > b[0]) return true;
if (a[0] < b[0]) return false;
if (a[1] > b[1]) return true;
if (a[1] < b[1]) return false;
if (a[2] > b[2]) return true;
if (a[2] < b[2]) return false;
return true;
}
Here is another short version that works with any number of sub versions, padded zeros and even numbers with letters (1.0.0b3)
const compareVer = ((prep, repl) =>
{
prep = t => ("" + t)
//treat non-numerical characters as lower version
//replacing them with a negative number based on charcode of first character
.replace(/[^0-9\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
//remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-[0-9]+)(\.[0-9]+)?\.*$/g, "$1$2")
.split('.');
return (a, b, c, i, r) =>
{
a = prep(a);
b = prep(b);
for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
{
r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
}
return r;
}
})();
Function returns:
0 if a = b
1 if a > b
-1 if a < b
1.0 = 1.0.0.0.0.0
1.0 < 1.0.1
1.0b1 < 1.0
1.0b = 1.0b
1.1 > 1.0.1b
1.1alpha < 1.1beta
1.1rc1 > 1.1beta
1.1rc1 < 1.1rc2
1.1.0a1 < 1.1a2
1.1.0a10 > 1.1.0a1
1.1.0alpha = 1.1a
1.1.0alpha2 < 1.1b1
1.0001 > 1.00000.1.0.0.0.01
/*use strict*/
const compareVer = ((prep, repl) =>
{
prep = t => ("" + t)
//treat non-numerical characters as lower version
//replacing them with a negative number based on charcode of first character
.replace(/[^0-9\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
//remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-[0-9]+)(\.[0-9]+)?\.*$/g, "$1$2")
.split('.');
return (a, b, c, i, r) =>
{
a = prep(a);
b = prep(b);
for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
{
r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
}
return r;
}
})();
//examples
let list = [
["1.0", "1.0.0.0.0.0"],
["1.0", "1.0.1"],
["1.0b1", "1.0"],
["1.0b", "1.0b"],
["1.1", "1.0.1b"],
["1.1alpha", "1.1beta"],
["1.1rc1", "1.1beta"],
["1.1rc1", "1.1rc2"],
["1.1.0a1", "1.1a2"],
["1.1.0a10", "1.1.0a1"],
["1.1.0alpha", "1.1a"],
["1.1.0alpha2", "1.1b1"],
["1.0001", "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
console.log( list[i][0] + " " + "<=>"[compareVer(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}
https://jsfiddle.net/vanowm/p7uvtbor/
Couldn't find a function doing what I wanted here. So I wrote my own. This is my contribution. I hope someone find it useful.
Pros:
Handles version strings of arbitrary length. '1' or '1.1.1.1.1'.
Defaults each value to 0 if not specified. Just because a string is longer doesn't mean it's a bigger version. ('1' should be the same as '1.0' and '1.0.0.0'.)
Compare numbers not strings. ('3'<'21' should be true. Not false.)
Don't waste time on useless compares in the loop. (Comparing for ==)
You can choose your own comparator.
Cons:
It does not handle letters in the version string. (I don't know how that would even work?)
My code, similar to the accepted answer by Jon:
function compareVersions(v1, comparator, v2) {
"use strict";
var comparator = comparator == '=' ? '==' : comparator;
if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
throw new Error('Invalid comparator. ' + comparator);
}
var v1parts = v1.split('.'), v2parts = v2.split('.');
var maxLen = Math.max(v1parts.length, v2parts.length);
var part1, part2;
var cmp = 0;
for(var i = 0; i < maxLen && !cmp; i++) {
part1 = parseInt(v1parts[i], 10) || 0;
part2 = parseInt(v2parts[i], 10) || 0;
if(part1 < part2)
cmp = 1;
if(part1 > part2)
cmp = -1;
}
return eval('0' + comparator + cmp);
}
Examples:
compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false
2017 answer:
v1 = '20.0.12';
v2 = '3.123.12';
compareVersions(v1,v2)
// return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2
function compareVersions(v1, v2) {
v1= v1.split('.')
v2= v2.split('.')
var len = Math.max(v1.length,v2.length)
/*default is true*/
for( let i=0; i < len; i++)
v1 = Number(v1[i] || 0);
v2 = Number(v2[i] || 0);
if (v1 !== v2) return v1 - v2 ;
i++;
}
return 0;
}
Simplest code for modern browsers:
function compareVersion2(ver1, ver2) {
ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
return ver1 <= ver2;
}
The idea here is to compare numbers but in the form of string. to make the comparison work the two strings must be at the same length. so:
"123" > "99" become "123" > "099"
padding the short number "fix" the comparison
Here I padding each part with zeros to lengths of 10. then just use simple string compare for the answer
Example :
var ver1 = '0.2.10', ver2=`0.10.2`
//become
ver1 = '0000000000.0000000002.0000000010'
ver2 = '0000000000.0000000010.0000000002'
// then it easy to see that
ver1 <= ver2 // true
I faced the similar issue, and I had already created a solution for it. Feel free to give it a try.
It returns 0 for equal, 1 if the version is greater and -1 if it is less
function compareVersion(currentVersion, minVersion) {
let current = currentVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
let min = minVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
for(let i = 0; i < Math.max(current.length, min.length); i++) {
if((current[i] || 0) < (min[i] || 0)) {
return -1
} else if ((current[i] || 0) > (min[i] || 0)) {
return 1
}
}
return 0
}
console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
console.log(compareVersion("4.4.0","4.4.1"));
console.log(compareVersion("5.24","5.2"));
console.log(compareVersion("4.1","4.1.2"));
console.log(compareVersion("4.1.2","4.1"));
console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("0","1"));
console.log(compareVersion("1","1"));
console.log(compareVersion("1","1.0.00000.0000"));
console.log(compareVersion("","1"));
console.log(compareVersion("10.0.1","10.1"));
Although this question already has a lot of answers, each one promotes their own backyard-brewn solution, whilst we have a whole ecosystem of (battle-)tested libraries for this.
A quick search on NPM, GitHub, X will give us some lovely libs, and I'd want to run through some:
semver-compare is a great lightweight (~230 bytes) lib that's especially useful if you want to sort by version numbers, as the library's exposed method returns -1, 0 or 1 appropriately.
The core of the library:
module.exports = function cmp (a, b) {
var pa = a.split('.');
var pb = b.split('.');
for (var i = 0; i < 3; i++) {
var na = Number(pa[i]);
var nb = Number(pb[i]);
if (na > nb) return 1;
if (nb > na) return -1;
if (!isNaN(na) && isNaN(nb)) return 1;
if (isNaN(na) && !isNaN(nb)) return -1;
}
return 0;
};
compare-semver is rather hefty in size (~4.4 kB gzipped), but allows for some nice unique comparisons like to find the minimum/maximum of a stack of versions or to find out if the provided version is unique or less than anything else in a collection of versions.
compare-versions is another small library (~630 bytes gzipped) and follows the spec nicely, meaning you can compare versions with alpha/beta flags and even wildcards (like for minor/patch versions: 1.0.x or 1.0.*)
The point being: there's not always a need to copy-paste code from Stack Overflow, if you can find decent, (unit-)tested versions via your package manager of choice.
Forgive me if this idea already been visited in a link I have not seen.
I have had some success with conversion of the parts into a weighted sum like so:
partSum = this.major * Math.Pow(10,9);
partSum += this.minor * Math.Pow(10, 6);
partSum += this.revision * Math.Pow(10, 3);
partSum += this.build * Math.Pow(10, 0);
Which made comparisons very easy (comparing a double).
Our version fields are never more than 4 digits.
7.10.2.184 -> 7010002184.0
7.11.0.1385 -> 7011001385.0
I hope this helps someone, as the multiple conditionals seem a bit overkill.
We can now use Intl.Collator API now to create numeric comparators. Browser support is pretty decent, but not supported in Node.js at the time of writing.
const semverCompare = new Intl.Collator("en", { numeric: true }).compare;
const versions = ['1.0.1', '1.10.2', '1.1.1', '1.10.1', '1.5.10', '2.10.0', '2.0.1'];
console.log(versions.sort(semverCompare))
const example2 = ["1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"];
console.log(example2.sort(semverCompare))
A dead simple way:
function compareVer(previousVersion, currentVersion) {
try {
const [prevMajor, prevMinor = 0, prevPatch = 0] = previousVersion.split('.').map(Number);
const [curMajor, curMinor = 0, curPatch = 0] = currentVersion.split('.').map(Number);
if (curMajor > prevMajor) {
return 'major update';
}
if (curMajor < prevMajor) {
return 'major downgrade';
}
if (curMinor > prevMinor) {
return 'minor update';
}
if (curMinor < prevMinor) {
return 'minor downgrade';
}
if (curPatch > prevPatch) {
return 'patch update';
}
if (curPatch < prevPatch) {
return 'patch downgrade';
}
return 'same version';
} catch (e) {
return 'invalid format';
}
}
Output:
compareVer("3.1", "3.1.1") // patch update
compareVer("3.1.1", "3.2") // minor update
compareVer("2.1.1", "1.1.1") // major downgrade
compareVer("1.1.1", "1.1.1") // same version
Check the function version_compare() from the php.js project. It's is similar to PHP's version_compare().
You can simply use it like this:
version_compare('2.0', '2.0.0.1', '<');
// returns true
My less verbose answer than most of the answers here
/**
* Compare two semver versions. Returns true if version A is greater than
* version B
* #param {string} versionA
* #param {string} versionB
* #returns {boolean}
*/
export const semverGreaterThan = function(versionA, versionB){
var versionsA = versionA.split(/\./g),
versionsB = versionB.split(/\./g)
while (versionsA.length || versionsB.length) {
var a = Number(versionsA.shift()), b = Number(versionsB.shift())
if (a == b)
continue
return (a > b || isNaN(b))
}
return false
}
You could use String#localeCompare with options
sensitivity
Which differences in the strings should lead to non-zero result values. Possible values are:
"base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.
"accent": Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples: a ≠ b, a ≠ á, a = A.
"case": Only strings that differ in base letters or case compare as unequal. Examples: a ≠ b, a = á, a ≠ A.
"variant": Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal. Other differences may also be taken into consideration. Examples: a ≠ b, a ≠ á, a ≠ A.
The default is "variant" for usage "sort"; it's locale dependent for usage "search".
numeric
Whether numeric collation should be used, such that "1" < "2" < "10". Possible values are true and false; the default is false. This option can be set through an options property or through a Unicode extension key; if both are provided, the options property takes precedence. Implementations are not required to support this property.
var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
console.log(versions);
The (most of the time) correct JavaScript answer in 2020
Both Nina Scholz in March 2020 and Sid Vishnoi in April 2020 post the modern answer:
var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
versions.sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
);
console.log(versions);
localCompare has been around for some time
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator
But what about 1.0a and 1.0.1
localCompare doesn't solve that, still returns 1.0.1 , 1.0a
Michael Deal in his (longish &complex) solution already cracked that in 2013
He converts Numbers to another Base, so they can be sorted better
His answer got me thinking...
666 - Don't think in numbers - 999
Sorting is alphanumeric, based on the ASCII values, so let's (ab)use ASCII as the "base"
My solution is to convert 1.0.2.1 to b.a.c.b to bacb , and then sort
This solves 1.1 vs. 1.0.0.0.1 with: bb vs. baaab
And immediately solves the 1.0a and 1.0.1 sorting problem with notation: baa and bab
Conversion is done with:
const str = s => s.match(/(\d+)|[a-z]/g)
.map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
= Calculate ASCII value for 0...999 Numbers, otherwise concat letter
1.0a >>> [ "1" , "0" , "a" ] >>> [ "b" , "a" , "a" ]
For comparison sake there is no need to concatenate it to one string with .join("")
Oneliner
const sortVersions=(x,v=s=>s.match(/(\d+)|[a-z]/g)
.map(c=>c==~~c?String.fromCharCode(97+c):c))
=>x.sort((a,b)=>v(b)<v(a)?1:-1)
Test snippet:
function log(label,val){
document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}
let v = ["1.90.1", "1.9.1", "1.89", "1.090", "1.2", "1.0a", "1.0.1", "1.10", "1.0.0a"];
log('not sorted input :',v);
v.sort((a, b) => a.localeCompare(b,undefined,{numeric:true,sensitivity:'base' }));
log(' locale Compare :', v); // 1.0a AFTER 1.0.1
const str = s => s.match(/(\d+)|[a-z]/g)
.map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
const versionCompare = (a, b) => {
a = str(a);
b = str(b);
return b < a ? 1 : a == b ? 0 : -1;
}
v.sort(versionCompare);
log('versionCompare:', v);
Note how 1.090 is sorted in both results.
My code will not solve the 001.012.001 notation mentioned in one answer, but the localeCompare gets that part of the challenge right.
You could combine the two methods:
sort with .localCompare OR versionCompare when there is a letter involved
Final JavaScript solution
const sortVersions = (
x,
v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/)
? v(b) < v(a) ? 1 : -1
: a.localeCompare(b, 0, {numeric: true}))
let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1"];
console.log(sortVersions(v));
Few lines of code and good if you don't want to allow letters or symbols. This works if you control the versioning scheme and it's not something a 3rd party provides.
// we presume all versions are of this format "1.4" or "1.10.2.3", without letters
// returns: 1 (bigger), 0 (same), -1 (smaller)
function versionCompare (v1, v2) {
const v1Parts = v1.split('.')
const v2Parts = v2.split('.')
const length = Math.max(v1Parts.length, v2Parts.length)
for (let i = 0; i < length; i++) {
const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0)
if (value < 0) return -1
if (value > 0) return 1
}
return 0
}
console.log(versionCompare('1.2.0', '1.2.4') === -1)
console.log(versionCompare('1.2', '1.2.0') === 0)
console.log(versionCompare('1.2', '1') === 1)
console.log(versionCompare('1.2.10', '1.2.1') === 1)
console.log(versionCompare('1.2.134230', '1.2.2') === 1)
console.log(versionCompare('1.2.134230', '1.3.0.1.2.3.1') === -1)
You can use a JavaScript localeCompare method:
a.localeCompare(b, undefined, { numeric: true })
Here is an example:
"1.1".localeCompare("2.1.1", undefined, { numeric: true }) => -1
"1.0.0".localeCompare("1.0", undefined, { numeric: true }) => 1
"1.0.0".localeCompare("1.0.0", undefined, { numeric: true }) => 0
// Returns true if v1 is bigger than v2, and false if otherwise.
function isNewerThan(v1, v2) {
v1=v1.split('.');
v2=v2.split('.');
for(var i = 0; i<Math.max(v1.length,v2.length); i++){
if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
if(v1[i] > v2[i]) return true;
if(v1[i] < v2[i]) return false;
}
return false; // Returns false if they are equal
}
The idea is to compare two versions and know which is the biggest. We delete "." and we compare each position of the vector with the other.
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compareVersions(a_components, b_components) {
if (a_components === b_components) {
return 0;
}
var partsNumberA = a_components.split(".");
var partsNumberB = b_components.split(".");
for (var i = 0; i < partsNumberA.length; i++) {
var valueA = parseInt(partsNumberA[i]);
var valueB = parseInt(partsNumberB[i]);
// A bigger than B
if (valueA > valueB || isNaN(valueB)) {
return 1;
}
// B bigger than A
if (valueA < valueB) {
return -1;
}
}
}
The replace() function only replaces the first occurence in the string. So, lets replace the . with ,. Afterwards delete all . and make the , to . again and parse it to float.
for(i=0; i<versions.length; i++) {
v = versions[i].replace('.', ',');
v = v.replace(/\./g, '');
versions[i] = parseFloat(v.replace(',', '.'));
}
finally, sort it:
versions.sort();
Check out this blog post. This function works for numeric version numbers.
function compVersions(strV1, strV2) {
var nRes = 0
, parts1 = strV1.split('.')
, parts2 = strV2.split('.')
, nLen = Math.max(parts1.length, parts2.length);
for (var i = 0; i < nLen; i++) {
var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
, nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;
if (isNaN(nP1)) { nP1 = 0; }
if (isNaN(nP2)) { nP2 = 0; }
if (nP1 != nP2) {
nRes = (nP1 > nP2) ? 1 : -1;
break;
}
}
return nRes;
};
compVersions('10', '10.0'); // 0
compVersions('10.1', '10.01.0'); // 0
compVersions('10.0.1', '10.0'); // 1
compVersions('10.0.1', '10.1'); // -1
If, for example, we want to check if the current jQuery version is less than 1.8, parseFloat($.ui.version) < 1.8 ) would give a wrong result if version is "1.10.1", since parseFloat("1.10.1") returns 1.1.
A string compare would also go wrong, since "1.8" < "1.10" evaluates to false.
So we need a test like this
if(versionCompare($.ui.version, "1.8") < 0){
alert("please update jQuery");
}
The following function handles this correctly:
/** Compare two dotted version strings (like '10.2.3').
* #returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
*/
function versionCompare(v1, v2) {
var v1parts = ("" + v1).split("."),
v2parts = ("" + v2).split("."),
minLength = Math.min(v1parts.length, v2parts.length),
p1, p2, i;
// Compare tuple pair-by-pair.
for(i = 0; i < minLength; i++) {
// Convert to integer if possible, because "8" > "10".
p1 = parseInt(v1parts[i], 10);
p2 = parseInt(v2parts[i], 10);
if (isNaN(p1)){ p1 = v1parts[i]; }
if (isNaN(p2)){ p2 = v2parts[i]; }
if (p1 == p2) {
continue;
}else if (p1 > p2) {
return 1;
}else if (p1 < p2) {
return -1;
}
// one operand is NaN
return NaN;
}
// The longer tuple is always considered 'greater'
if (v1parts.length === v2parts.length) {
return 0;
}
return (v1parts.length < v2parts.length) ? -1 : 1;
}
Here are some examples:
// compare dotted version strings
console.assert(versionCompare("1.8", "1.8.1") < 0);
console.assert(versionCompare("1.8.3", "1.8.1") > 0);
console.assert(versionCompare("1.8", "1.10") < 0);
console.assert(versionCompare("1.10.1", "1.10.1") === 0);
// Longer is considered 'greater'
console.assert(versionCompare("1.10.1.0", "1.10.1") > 0);
console.assert(versionCompare("1.10.1", "1.10.1.0") < 0);
// Strings pairs are accepted
console.assert(versionCompare("1.x", "1.x") === 0);
// Mixed int/string pairs return NaN
console.assert(isNaN(versionCompare("1.8", "1.x")));
//works with plain numbers
console.assert(versionCompare("4", 3) > 0);
See here for a live sample and test suite:
http://jsfiddle.net/mar10/8KjvP/
This is a neat trick. If you are dealing with numeric values, between a specific range of values, you can assign a value to each level of the version object. For instance "largestValue" is set to 0xFF here, which creates a very "IP" sort of look to your versioning.
This also handles alpha-numeric versioning (i.e. 1.2a < 1.2b)
// The version compare function
function compareVersion(data0, data1, levels) {
function getVersionHash(version) {
var value = 0;
version = version.split(".").map(function (a) {
var n = parseInt(a);
var letter = a.replace(n, "");
if (letter) {
return n + letter[0].charCodeAt() / 0xFF;
} else {
return n;
}
});
for (var i = 0; i < version.length; ++i) {
if (levels === i) break;
value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
}
return value;
};
var v1 = getVersionHash(data0);
var v2 = getVersionHash(data1);
return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
// Returns 0 or 1, correlating to input A and input B
// Direct match returns -1
var version = compareVersion("1.254.253", "1.254.253a", 3);
I made this based on Kons idea, and optimized it for Java version "1.7.0_45". It's just a function meant to convert a version string to a float. This is the function:
function parseVersionFloat(versionString) {
var versionArray = ("" + versionString)
.replace("_", ".")
.replace(/[^0-9.]/g, "")
.split("."),
sum = 0;
for (var i = 0; i < versionArray.length; ++i) {
sum += Number(versionArray[i]) / Math.pow(10, i * 3);
}
console.log(versionString + " -> " + sum);
return sum;
}
String "1.7.0_45" is converted to 1.0070000450000001 and this is good enough for a normal comparison. Error explained here: How to deal with floating point number precision in JavaScript?. If need more then 3 digits on any part you can change the divider Math.pow(10, i * 3);.
Output will look like this:
1.7.0_45 > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890 > 1.23456789
Here's a coffeescript implementation suitable for use with Array.sort inspired by other answers here:
# Returns > 0 if v1 > v2 and < 0 if v1 < v2 and 0 if v1 == v2
compareVersions = (v1, v2) ->
v1Parts = v1.split('.')
v2Parts = v2.split('.')
minLength = Math.min(v1Parts.length, v2Parts.length)
if minLength > 0
for idx in [0..minLength - 1]
diff = Number(v1Parts[idx]) - Number(v2Parts[idx])
return diff unless diff is 0
return v1Parts.length - v2Parts.length
I wrote a node module for sorting versions, you can find it here: version-sort
Features:
no limit of sequences '1.0.1.5.53.54654.114.1.154.45' works
no limit of sequence length: '1.1546515465451654654654654138754431574364321353734' works
can sort objects by version (see README)
stages (like alpha, beta, rc1, rc2)
Do not hesitate to open an issue if you need an other feature.
I need to convert a Google Spreadsheet column index into its corresponding letter value, for example, given a spreadsheet:
I need to do this (this function obviously does not exist, it's an example):
getColumnLetterByIndex(4); // this should return "D"
getColumnLetterByIndex(1); // this should return "A"
getColumnLetterByIndex(6); // this should return "F"
Now, I don't recall exactly if the index starts from 0 or from 1, anyway the concept should be clear.
I didn't find anything about this on gas documentation.. am I blind? Any idea?
Thank you
I wrote these a while back for various purposes (will return the double-letter column names for column numbers > 26):
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
function letterToColumn(letter)
{
var column = 0, length = letter.length;
for (var i = 0; i < length; i++)
{
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
This works good
=REGEXEXTRACT(ADDRESS(ROW(); COLUMN()); "[A-Z]+")
even for columns beyond Z.
Simply replace COLUMN() with your column number. The value of ROW() doesn't matter.
No need to reinvent the wheel here, use the GAS range instead:
var column_index = 1; // your column to resolve
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(1, column_index, 1, 1);
Logger.log(range.getA1Notation().match(/([A-Z]+)/)[0]); // Logs "A"
=SUBSTITUTE(ADDRESS(1,COLUMN(),4), "1", "")
This takes your cell, gets it's address as e.g. C1, and removes the "1".
How it works
COLUMN() gives the number of the column of the cell.
ADDRESS(1, ..., <format>) gives an address of a cell, in format speficied by <format> parameter. 4 means the address you know - e.g. C1.
The row doesn't matter here, so we use 1.
See ADDRESS docs
Finally, SUBSTITUTE(..., "1", "") replaces the 1 in the address C1, so you're left with the column letter.
This works on ranges A-Z
formula =char(64+column())
js String.fromCharCode(64+colno)
an google spreadsheet appscript code, based on #Gardener would be:
function columnName(index) {
var cname = String.fromCharCode(65 + ((index - 1) % 26));
if (index > 26)
cname = String.fromCharCode(64 + (index - 1) / 26) + cname;
return cname;
}
In javascript:
X = (n) => (a=Math.floor(n/26)) >= 0 ? X(a-1) + String.fromCharCode(65+(n%26)) : '';
console.assert (X(0) == 'A')
console.assert (X(25) == 'Z')
console.assert (X(26) == 'AA')
console.assert (X(51) == 'AZ')
console.assert (X(52) == 'BA')
Adding to #SauloAlessandre's answer, this will work for columns up from A-ZZ.
=if(column() >26,char(64+(column()-1)/26),) & char(65 + mod(column()-1,26))
I like the answers by #wronex and #Ondra Žižka. However, I really like the simplicity of #SauloAlessandre's answer.
So, I just added the obvious code to allow #SauloAlessandre's answer to work for wider spreadsheets.
As #Dave mentioned in his comment, it does help to have a programming background, particularly one in C where we added the hex value of 'A' to a number to get the nth letter of the alphabet as a standard pattern.
Answer updated to catch the error pointed out by #Sangbok Lee. Thank you!
I was looking for a solution in PHP. Maybe this will help someone.
<?php
$numberToLetter = function(int $number)
{
if ($number <= 0) return null;
$temp; $letter = '';
while ($number > 0) {
$temp = ($number - 1) % 26;
$letter = chr($temp + 65) . $letter;
$number = ($number - $temp - 1) / 26;
}
return $letter;
};
$letterToNumber = function(string $letters) {
$letters = strtoupper($letters);
$letters = preg_replace("/[^A-Z]/", '', $letters);
$column = 0;
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$column += (ord($letters[$i]) - 64) * pow(26, $length - $i - 1);
}
return $column;
};
var_dump($numberToLetter(-1));
var_dump($numberToLetter(26));
var_dump($numberToLetter(27));
var_dump($numberToLetter(30));
var_dump($letterToNumber('-1A!'));
var_dump($letterToNumber('A'));
var_dump($letterToNumber('B'));
var_dump($letterToNumber('Y'));
var_dump($letterToNumber('Z'));
var_dump($letterToNumber('AA'));
var_dump($letterToNumber('AB'));
Output:
NULL
string(1) "Z"
string(2) "AA"
string(2) "AD"
int(1)
int(1)
int(2)
int(25)
int(26)
int(27)
int(28)
Simple way through Google Sheet functions, A to Z.
=column(B2) : value is 2
=address(1, column(B2)) : value is $B$1
=mid(address(1, column(B2)),2,1) : value is B
It's a complicated way through Google Sheet functions, but it's also more than AA.
=mid(address(1, column(AB3)),2,len(address(1, column(AB3)))-3) : value is AB
I also was looking for a Python version here is mine which was tested on Python 3.6
def columnToLetter(column):
character = chr(ord('A') + column % 26)
remainder = column // 26
if column >= 26:
return columnToLetter(remainder-1) + character
else:
return character
A comment on my answer says you wanted a script function for it. All right, here we go:
function excelize(colNum) {
var order = 1, sub = 0, divTmp = colNum;
do {
divTmp -= order; sub += order; order *= 26;
divTmp = (divTmp - (divTmp % 26)) / 26;
} while(divTmp > 0);
var symbols = "0123456789abcdefghijklmnopqrstuvwxyz";
var tr = c => symbols[symbols.indexOf(c)+10];
return Number(colNum-sub).toString(26).split('').map(c=>tr(c)).join('');
}
This can handle any number JS can handle, I think.
Explanation:
Since this is not base26, we need to substract the base times order for each additional symbol ("digit"). So first we count the order of the resulting number, and at the same time count the number to substract. And then we convert it to base 26 and substract that, and then shift the symbols to A-Z instead of 0-P.
Anyway, this question is turning into a code golf :)
Java Apache POI
String columnLetter = CellReference.convertNumToColString(columnNumber);
This will cover you out as far as column AZ:
=iferror(if(match(A2,$A$1:$AZ$1,0)<27,char(64+(match(A2,$A$1:$AZ$1,0))),concatenate("A",char(38+(match(A2,$A$1:$AZ$1,0))))),"No match")
A function to convert a column index to letter combinations, recursively:
function lettersFromIndex(index, curResult, i) {
if (i == undefined) i = 11; //enough for Number.MAX_SAFE_INTEGER
if (curResult == undefined) curResult = "";
var factor = Math.floor(index / Math.pow(26, i)); //for the order of magnitude 26^i
if (factor > 0 && i > 0) {
curResult += String.fromCharCode(64 + factor);
curResult = lettersFromIndex(index - Math.pow(26, i) * factor, curResult, i - 1);
} else if (factor == 0 && i > 0) {
curResult = lettersFromIndex(index, curResult, i - 1);
} else {
curResult += String.fromCharCode(64 + index % 26);
}
return curResult;
}
function lettersFromIndex(index, curResult, i) {
if (i == undefined) i = 11; //enough for Number.MAX_SAFE_INTEGER
if (curResult == undefined) curResult = "";
var factor = Math.floor(index / Math.pow(26, i));
if (factor > 0 && i > 0) {
curResult += String.fromCharCode(64 + factor);
curResult = lettersFromIndex(index - Math.pow(26, i) * factor, curResult, i - 1);
} else if (factor == 0 && i > 0) {
curResult = lettersFromIndex(index, curResult, i - 1);
} else {
curResult += String.fromCharCode(64 + index % 26);
}
return curResult;
}
document.getElementById("result1").innerHTML = lettersFromIndex(32);
document.getElementById("result2").innerHTML = lettersFromIndex(6800);
document.getElementById("result3").innerHTML = lettersFromIndex(9007199254740991);
32 --> <span id="result1"></span><br> 6800 --> <span id="result2"></span><br> 9007199254740991 --> <span id="result3"></span>
In python, there is the gspread library
import gspread
column_letter = gspread.utils.rowcol_to_a1(1, <put your col number here>)[:-1]
If you cannot use python, I suggest looking the source code of rowcol_to_a1() in https://github.com/burnash/gspread/blob/master/gspread/utils.py
Here's a two liner which works beyond ZZ using recursion:
Python
def col_to_letter(n):
l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
return col_to_letter((n-1)//26) + col_to_letter(n%26) if n > 26 else l[n-1]
Javascript
function colToLetter(n) {
l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
return n > 26 ? colToLetter(Math.floor((n-1)/26)) + colToLetter(n%26) : l[n-1]
}
If you need a version directly in the sheet, here a solution:
For the colonne 4, we can use :
=Address(1,4)
I keep the row number to 1 for simplicty.
The above formula returns $D$1 which is not what you want.
By modifying the formula a little bit we can remove the dollar signs in the cell reference.
=Address(1,4,4)
Adding four as the third argument tells the formula that we are not looking for absolute cell reference.
Now the returns is : D1
So you only need to remove the 1 to get the colonne lettre if you need, for example with :
=Substitute(Address(1,4,4),"1","")
That returns D.
This is a way to convert column letters to column numbers.
=mmult(ArrayFormula(ifna(vlookup(substitute(mid(rept(" ",3-len(filter(A:A,A:A<>"")))&filter(A:A,A:A<>""),sequence(1,3),1)," ",""),{char(64+sequence(26)),sequence(26)},2,0),0)*{676,26,1}),sequence(3,1,1,0))
Screenshot of the Google Sheet
Don't use 26 radix. Like below.
const n2c = n => {
if (!n) return '';
// Column number to 26 radix. From 0 to p.
// Column number starts from 1. Subtract 1.
return [...(n-1).toString(26)]
// to ascii number
.map(c=>c.charCodeAt())
.map((c,i,arr)=> {
// last digit
if (i===arr.length-1) return c;
// 10 -> p
else if (arr.length - i > 2 && arr[i+1]===48) return c===49 ? null : c-2;
// 0 -> p
else if (c===48) return 112;
// a-1 -> 9
else if (c===97) return 57;
// Subtract 1 except last digit.
// Look at 10. This should be AA not BA.
else return c-1;
})
.filter(c=>c!==null)
// Convert with the ascii table. [0-9]->[A-J] and [a-p]->[K-Z]
.map(a=>a>96?a-22:a+17)
// to char
.map(a=>String.fromCharCode(a))
.join('');
};
const table = document.createElement('table');
table.border = 1;
table.cellPadding = 3;
for(let i=0, row; i<1380; i++) {
if (i%5===0) row = table.insertRow();
row.insertCell().textContent = i;
row.insertCell().textContent = n2c(i);
}
document.body.append(table);
td:nth-child(odd) { background: gray; color: white; }
td:nth-child(even) { background: silver; }
Simple typescript functional approach
const integerToColumn = (integer: number): string => {
const base26 = (x: number): string =>
x < 26
? String.fromCharCode(65 + x)
: base26((x / 26) - 1) + String.fromCharCode(65 + x % 26)
return base26(integer)
}
console.log(integerToColumn(0)) // "A"
console.log(integerToColumn(1)) // "B"
console.log(integerToColumn(2)) // "C"
Here is a general version written in Scala. It's for a column index start at 0 (it's simple to modify for an index start at 1):
def indexToColumnBase(n: Int, base: Int): String = {
require(n >= 0, s"Index is non-negative, n = $n")
require(2 <= base && base <= 26, s"Base in range 2...26, base = $base")
def digitFromZeroToLetter(n: BigInt): String =
('A' + n.toInt).toChar.toString
def digitFromOneToLetter(n: BigInt): String =
('A' - 1 + n.toInt).toChar.toString
def lhsConvert(n: Int): String = {
val q0: Int = n / base
val r0: Int = n % base
val q1 = if (r0 == 0) (n - base) / base else q0
val r1 = if (r0 == 0) base else r0
if (q1 == 0)
digitFromOneToLetter(r1)
else
lhsConvert(q1) + digitFromOneToLetter(r1)
}
val q: Int = n / base
val r: Int = n % base
if (q == 0)
digitFromZeroToLetter(r)
else
lhsConvert(q) + digitFromZeroToLetter(r)
}
def indexToColumnAtoZ(n: Int): String = {
val AtoZBase = 26
indexToColumnBase(n, AtoZBase)
}
In PowerShell:
function convert-IndexToColumn
{
Param
(
[Parameter(Mandatory)]
[int]$col
)
"$(if($col -gt 26){[char][int][math]::Floor(64+($col-1)/26)})$([char](65 + (($col-1) % 26)))"
}
Here is a 0-indexed JavaScript function without a maximum value, as it uses a while-loop:
function indexesToA1Notation(row, col) {
const letterCount = 'Z'.charCodeAt() - 'A'.charCodeAt() + 1;
row += 1
let colName = ''
while (col >= 0) {
let rem = col % letterCount
colName = String.fromCharCode('A'.charCodeAt() + rem)
col -= rem
col /= letterCount
}
return `${colName}${row}`
}
//Test runs:
console.log(indexesToA1Notation(0,0)) //A1
console.log(indexesToA1Notation(37,9)) //J38
console.log(indexesToA1Notation(5,747)) //ABT6
I wrote it for a web-app, so I'm not 100% sure it works in Google Apps Script, but it is normal JavaScript, so I assume it will.
For some reason I cant get the snippet to show its output, but you can copy the code to some online playground if you like
Here's a zero-indexed version (in Python):
letters = []
while column >= 0:
letters.append(string.ascii_uppercase[column % 26])
column = column // 26 - 1
return ''.join(reversed(letters))
Here is the software version number:
"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"
How can I compare this?
Assume the correct order is:
"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"
The idea is simple...:
Read the first digit, than, the second, after that the third...
But I can't convert the version number to float number...
You also can see the version number like this:
"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"
And this is clearer to see what is the idea behind...
But, how can I convert it into a computer program?
semver
The semantic version parser used by npm.
$ npm install semver
var semver = require('semver');
semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false
semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')
Semantic Versioning Link : https://www.npmjs.com/package/semver#prerelease-identifiers
The basic idea to make this comparison would be to use Array.split to get arrays of parts from the input strings and then compare pairs of parts from the two arrays; if the parts are not equal we know which version is smaller.
There are a few of important details to keep in mind:
How should the parts in each pair be compared? The question wants to compare numerically, but what if we have version strings that are not made up of just digits (e.g. "1.0a")?
What should happen if one version string has more parts than the other? Most likely "1.0" should be considered less than "1.0.1", but what about "1.0.0"?
Here's the code for an implementation that you can use directly (gist with documentation):
function versionCompare(v1, v2, options) {
var lexicographical = options && options.lexicographical,
zeroExtend = options && options.zeroExtend,
v1parts = v1.split('.'),
v2parts = v2.split('.');
function isValidPart(x) {
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
}
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
return NaN;
}
if (zeroExtend) {
while (v1parts.length < v2parts.length) v1parts.push("0");
while (v2parts.length < v1parts.length) v2parts.push("0");
}
if (!lexicographical) {
v1parts = v1parts.map(Number);
v2parts = v2parts.map(Number);
}
for (var i = 0; i < v1parts.length; ++i) {
if (v2parts.length == i) {
return 1;
}
if (v1parts[i] == v2parts[i]) {
continue;
}
else if (v1parts[i] > v2parts[i]) {
return 1;
}
else {
return -1;
}
}
if (v1parts.length != v2parts.length) {
return -1;
}
return 0;
}
This version compares parts naturally, does not accept character suffixes and considers "1.7" to be smaller than "1.7.0". The comparison mode can be changed to lexicographical and shorter version strings can be automatically zero-padded using the optional third argument.
There is a JSFiddle that runs "unit tests" here; it is a slightly expanded version of ripper234's work (thank you).
Important note: This code uses Array.map and Array.every, which means that it will not run in IE versions earlier than 9. If you need to support those you will have to provide polyfills for the missing methods.
The simplest is to use localeCompare :
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
This will return:
0: version strings are equal
1: version a is greater than b
-1: version b is greater than a
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
if (a === b) {
return 0;
}
var a_components = a.split(".");
var b_components = b.split(".");
var len = Math.min(a_components.length, b_components.length);
// loop while the components are equal
for (var i = 0; i < len; i++) {
// A bigger than B
if (parseInt(a_components[i]) > parseInt(b_components[i])) {
return 1;
}
// B bigger than A
if (parseInt(a_components[i]) < parseInt(b_components[i])) {
return -1;
}
}
// If one's a prefix of the other, the longer one is greater.
if (a_components.length > b_components.length) {
return 1;
}
if (a_components.length < b_components.length) {
return -1;
}
// Otherwise they are the same.
return 0;
}
console.log(compare("1", "2"));
console.log(compare("2", "1"));
console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));
This very small, yet very fast compare function takes version numbers of any length and any number size per segment.
Return values:
- a number < 0 if a < b
- a number > 0 if a > b
- 0 if a = b
So you can use it as compare function for Array.sort();
EDIT: Bugfixed Version stripping trailing zeros to recognize "1" and "1.0.0" as equal
function cmpVersions (a, b) {
var i, diff;
var regExStrip0 = /(\.0+)+$/;
var segmentsA = a.replace(regExStrip0, '').split('.');
var segmentsB = b.replace(regExStrip0, '').split('.');
var l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}
}
return segmentsA.length - segmentsB.length;
}
// TEST
console.log(
['2.5.10.4159',
'1.0.0',
'0.5',
'0.4.1',
'1',
'1.1',
'0.0.0',
'2.5.0',
'2',
'0.0',
'2.5.10',
'10.5',
'1.25.4',
'1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]
Simple and short function:
function isNewerVersion (oldVer, newVer) {
const oldParts = oldVer.split('.')
const newParts = newVer.split('.')
for (var i = 0; i < newParts.length; i++) {
const a = ~~newParts[i] // parse int
const b = ~~oldParts[i] // parse int
if (a > b) return true
if (a < b) return false
}
return false
}
Tests:
isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false
Taken from http://java.com/js/deployJava.js:
// return true if 'installed' (considered as a JRE version string) is
// greater than or equal to 'required' (again, a JRE version string).
compareVersions: function (installed, required) {
var a = installed.split('.');
var b = required.split('.');
for (var i = 0; i < a.length; ++i) {
a[i] = Number(a[i]);
}
for (var i = 0; i < b.length; ++i) {
b[i] = Number(b[i]);
}
if (a.length == 2) {
a[2] = 0;
}
if (a[0] > b[0]) return true;
if (a[0] < b[0]) return false;
if (a[1] > b[1]) return true;
if (a[1] < b[1]) return false;
if (a[2] > b[2]) return true;
if (a[2] < b[2]) return false;
return true;
}
Here is another short version that works with any number of sub versions, padded zeros and even numbers with letters (1.0.0b3)
const compareVer = ((prep, repl) =>
{
prep = t => ("" + t)
//treat non-numerical characters as lower version
//replacing them with a negative number based on charcode of first character
.replace(/[^0-9\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
//remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-[0-9]+)(\.[0-9]+)?\.*$/g, "$1$2")
.split('.');
return (a, b, c, i, r) =>
{
a = prep(a);
b = prep(b);
for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
{
r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
}
return r;
}
})();
Function returns:
0 if a = b
1 if a > b
-1 if a < b
1.0 = 1.0.0.0.0.0
1.0 < 1.0.1
1.0b1 < 1.0
1.0b = 1.0b
1.1 > 1.0.1b
1.1alpha < 1.1beta
1.1rc1 > 1.1beta
1.1rc1 < 1.1rc2
1.1.0a1 < 1.1a2
1.1.0a10 > 1.1.0a1
1.1.0alpha = 1.1a
1.1.0alpha2 < 1.1b1
1.0001 > 1.00000.1.0.0.0.01
/*use strict*/
const compareVer = ((prep, repl) =>
{
prep = t => ("" + t)
//treat non-numerical characters as lower version
//replacing them with a negative number based on charcode of first character
.replace(/[^0-9\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toLowerCase().charCodeAt(0) - 65536) + ".")
//remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
.replace(/(?:\.0+)*(\.-[0-9]+)(\.[0-9]+)?\.*$/g, "$1$2")
.split('.');
return (a, b, c, i, r) =>
{
a = prep(a);
b = prep(b);
for (i = 0, r = 0, c = Math.max(a.length, b.length); !r && i++ < c;)
{
r = -1 * ((a[i] = ~~a[i]) < (b[i] = ~~b[i])) + (a[i] > b[i]);
}
return r;
}
})();
//examples
let list = [
["1.0", "1.0.0.0.0.0"],
["1.0", "1.0.1"],
["1.0b1", "1.0"],
["1.0b", "1.0b"],
["1.1", "1.0.1b"],
["1.1alpha", "1.1beta"],
["1.1rc1", "1.1beta"],
["1.1rc1", "1.1rc2"],
["1.1.0a1", "1.1a2"],
["1.1.0a10", "1.1.0a1"],
["1.1.0alpha", "1.1a"],
["1.1.0alpha2", "1.1b1"],
["1.0001", "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
console.log( list[i][0] + " " + "<=>"[compareVer(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}
https://jsfiddle.net/vanowm/p7uvtbor/
Couldn't find a function doing what I wanted here. So I wrote my own. This is my contribution. I hope someone find it useful.
Pros:
Handles version strings of arbitrary length. '1' or '1.1.1.1.1'.
Defaults each value to 0 if not specified. Just because a string is longer doesn't mean it's a bigger version. ('1' should be the same as '1.0' and '1.0.0.0'.)
Compare numbers not strings. ('3'<'21' should be true. Not false.)
Don't waste time on useless compares in the loop. (Comparing for ==)
You can choose your own comparator.
Cons:
It does not handle letters in the version string. (I don't know how that would even work?)
My code, similar to the accepted answer by Jon:
function compareVersions(v1, comparator, v2) {
"use strict";
var comparator = comparator == '=' ? '==' : comparator;
if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
throw new Error('Invalid comparator. ' + comparator);
}
var v1parts = v1.split('.'), v2parts = v2.split('.');
var maxLen = Math.max(v1parts.length, v2parts.length);
var part1, part2;
var cmp = 0;
for(var i = 0; i < maxLen && !cmp; i++) {
part1 = parseInt(v1parts[i], 10) || 0;
part2 = parseInt(v2parts[i], 10) || 0;
if(part1 < part2)
cmp = 1;
if(part1 > part2)
cmp = -1;
}
return eval('0' + comparator + cmp);
}
Examples:
compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false
2017 answer:
v1 = '20.0.12';
v2 = '3.123.12';
compareVersions(v1,v2)
// return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2
function compareVersions(v1, v2) {
v1= v1.split('.')
v2= v2.split('.')
var len = Math.max(v1.length,v2.length)
/*default is true*/
for( let i=0; i < len; i++)
v1 = Number(v1[i] || 0);
v2 = Number(v2[i] || 0);
if (v1 !== v2) return v1 - v2 ;
i++;
}
return 0;
}
Simplest code for modern browsers:
function compareVersion2(ver1, ver2) {
ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
return ver1 <= ver2;
}
The idea here is to compare numbers but in the form of string. to make the comparison work the two strings must be at the same length. so:
"123" > "99" become "123" > "099"
padding the short number "fix" the comparison
Here I padding each part with zeros to lengths of 10. then just use simple string compare for the answer
Example :
var ver1 = '0.2.10', ver2=`0.10.2`
//become
ver1 = '0000000000.0000000002.0000000010'
ver2 = '0000000000.0000000010.0000000002'
// then it easy to see that
ver1 <= ver2 // true
I faced the similar issue, and I had already created a solution for it. Feel free to give it a try.
It returns 0 for equal, 1 if the version is greater and -1 if it is less
function compareVersion(currentVersion, minVersion) {
let current = currentVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
let min = minVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
for(let i = 0; i < Math.max(current.length, min.length); i++) {
if((current[i] || 0) < (min[i] || 0)) {
return -1
} else if ((current[i] || 0) > (min[i] || 0)) {
return 1
}
}
return 0
}
console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
console.log(compareVersion("4.4.0","4.4.1"));
console.log(compareVersion("5.24","5.2"));
console.log(compareVersion("4.1","4.1.2"));
console.log(compareVersion("4.1.2","4.1"));
console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("0","1"));
console.log(compareVersion("1","1"));
console.log(compareVersion("1","1.0.00000.0000"));
console.log(compareVersion("","1"));
console.log(compareVersion("10.0.1","10.1"));
Although this question already has a lot of answers, each one promotes their own backyard-brewn solution, whilst we have a whole ecosystem of (battle-)tested libraries for this.
A quick search on NPM, GitHub, X will give us some lovely libs, and I'd want to run through some:
semver-compare is a great lightweight (~230 bytes) lib that's especially useful if you want to sort by version numbers, as the library's exposed method returns -1, 0 or 1 appropriately.
The core of the library:
module.exports = function cmp (a, b) {
var pa = a.split('.');
var pb = b.split('.');
for (var i = 0; i < 3; i++) {
var na = Number(pa[i]);
var nb = Number(pb[i]);
if (na > nb) return 1;
if (nb > na) return -1;
if (!isNaN(na) && isNaN(nb)) return 1;
if (isNaN(na) && !isNaN(nb)) return -1;
}
return 0;
};
compare-semver is rather hefty in size (~4.4 kB gzipped), but allows for some nice unique comparisons like to find the minimum/maximum of a stack of versions or to find out if the provided version is unique or less than anything else in a collection of versions.
compare-versions is another small library (~630 bytes gzipped) and follows the spec nicely, meaning you can compare versions with alpha/beta flags and even wildcards (like for minor/patch versions: 1.0.x or 1.0.*)
The point being: there's not always a need to copy-paste code from Stack Overflow, if you can find decent, (unit-)tested versions via your package manager of choice.
Forgive me if this idea already been visited in a link I have not seen.
I have had some success with conversion of the parts into a weighted sum like so:
partSum = this.major * Math.Pow(10,9);
partSum += this.minor * Math.Pow(10, 6);
partSum += this.revision * Math.Pow(10, 3);
partSum += this.build * Math.Pow(10, 0);
Which made comparisons very easy (comparing a double).
Our version fields are never more than 4 digits.
7.10.2.184 -> 7010002184.0
7.11.0.1385 -> 7011001385.0
I hope this helps someone, as the multiple conditionals seem a bit overkill.
We can now use Intl.Collator API now to create numeric comparators. Browser support is pretty decent, but not supported in Node.js at the time of writing.
const semverCompare = new Intl.Collator("en", { numeric: true }).compare;
const versions = ['1.0.1', '1.10.2', '1.1.1', '1.10.1', '1.5.10', '2.10.0', '2.0.1'];
console.log(versions.sort(semverCompare))
const example2 = ["1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"];
console.log(example2.sort(semverCompare))
A dead simple way:
function compareVer(previousVersion, currentVersion) {
try {
const [prevMajor, prevMinor = 0, prevPatch = 0] = previousVersion.split('.').map(Number);
const [curMajor, curMinor = 0, curPatch = 0] = currentVersion.split('.').map(Number);
if (curMajor > prevMajor) {
return 'major update';
}
if (curMajor < prevMajor) {
return 'major downgrade';
}
if (curMinor > prevMinor) {
return 'minor update';
}
if (curMinor < prevMinor) {
return 'minor downgrade';
}
if (curPatch > prevPatch) {
return 'patch update';
}
if (curPatch < prevPatch) {
return 'patch downgrade';
}
return 'same version';
} catch (e) {
return 'invalid format';
}
}
Output:
compareVer("3.1", "3.1.1") // patch update
compareVer("3.1.1", "3.2") // minor update
compareVer("2.1.1", "1.1.1") // major downgrade
compareVer("1.1.1", "1.1.1") // same version
Check the function version_compare() from the php.js project. It's is similar to PHP's version_compare().
You can simply use it like this:
version_compare('2.0', '2.0.0.1', '<');
// returns true
My less verbose answer than most of the answers here
/**
* Compare two semver versions. Returns true if version A is greater than
* version B
* #param {string} versionA
* #param {string} versionB
* #returns {boolean}
*/
export const semverGreaterThan = function(versionA, versionB){
var versionsA = versionA.split(/\./g),
versionsB = versionB.split(/\./g)
while (versionsA.length || versionsB.length) {
var a = Number(versionsA.shift()), b = Number(versionsB.shift())
if (a == b)
continue
return (a > b || isNaN(b))
}
return false
}
You could use String#localeCompare with options
sensitivity
Which differences in the strings should lead to non-zero result values. Possible values are:
"base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.
"accent": Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples: a ≠ b, a ≠ á, a = A.
"case": Only strings that differ in base letters or case compare as unequal. Examples: a ≠ b, a = á, a ≠ A.
"variant": Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal. Other differences may also be taken into consideration. Examples: a ≠ b, a ≠ á, a ≠ A.
The default is "variant" for usage "sort"; it's locale dependent for usage "search".
numeric
Whether numeric collation should be used, such that "1" < "2" < "10". Possible values are true and false; the default is false. This option can be set through an options property or through a Unicode extension key; if both are provided, the options property takes precedence. Implementations are not required to support this property.
var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
console.log(versions);
The (most of the time) correct JavaScript answer in 2020
Both Nina Scholz in March 2020 and Sid Vishnoi in April 2020 post the modern answer:
var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];
versions.sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
);
console.log(versions);
localCompare has been around for some time
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator
But what about 1.0a and 1.0.1
localCompare doesn't solve that, still returns 1.0.1 , 1.0a
Michael Deal in his (longish &complex) solution already cracked that in 2013
He converts Numbers to another Base, so they can be sorted better
His answer got me thinking...
666 - Don't think in numbers - 999
Sorting is alphanumeric, based on the ASCII values, so let's (ab)use ASCII as the "base"
My solution is to convert 1.0.2.1 to b.a.c.b to bacb , and then sort
This solves 1.1 vs. 1.0.0.0.1 with: bb vs. baaab
And immediately solves the 1.0a and 1.0.1 sorting problem with notation: baa and bab
Conversion is done with:
const str = s => s.match(/(\d+)|[a-z]/g)
.map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
= Calculate ASCII value for 0...999 Numbers, otherwise concat letter
1.0a >>> [ "1" , "0" , "a" ] >>> [ "b" , "a" , "a" ]
For comparison sake there is no need to concatenate it to one string with .join("")
Oneliner
const sortVersions=(x,v=s=>s.match(/(\d+)|[a-z]/g)
.map(c=>c==~~c?String.fromCharCode(97+c):c))
=>x.sort((a,b)=>v(b)<v(a)?1:-1)
Test snippet:
function log(label,val){
document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}
let v = ["1.90.1", "1.9.1", "1.89", "1.090", "1.2", "1.0a", "1.0.1", "1.10", "1.0.0a"];
log('not sorted input :',v);
v.sort((a, b) => a.localeCompare(b,undefined,{numeric:true,sensitivity:'base' }));
log(' locale Compare :', v); // 1.0a AFTER 1.0.1
const str = s => s.match(/(\d+)|[a-z]/g)
.map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
const versionCompare = (a, b) => {
a = str(a);
b = str(b);
return b < a ? 1 : a == b ? 0 : -1;
}
v.sort(versionCompare);
log('versionCompare:', v);
Note how 1.090 is sorted in both results.
My code will not solve the 001.012.001 notation mentioned in one answer, but the localeCompare gets that part of the challenge right.
You could combine the two methods:
sort with .localCompare OR versionCompare when there is a letter involved
Final JavaScript solution
const sortVersions = (
x,
v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/)
? v(b) < v(a) ? 1 : -1
: a.localeCompare(b, 0, {numeric: true}))
let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1"];
console.log(sortVersions(v));
Few lines of code and good if you don't want to allow letters or symbols. This works if you control the versioning scheme and it's not something a 3rd party provides.
// we presume all versions are of this format "1.4" or "1.10.2.3", without letters
// returns: 1 (bigger), 0 (same), -1 (smaller)
function versionCompare (v1, v2) {
const v1Parts = v1.split('.')
const v2Parts = v2.split('.')
const length = Math.max(v1Parts.length, v2Parts.length)
for (let i = 0; i < length; i++) {
const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0)
if (value < 0) return -1
if (value > 0) return 1
}
return 0
}
console.log(versionCompare('1.2.0', '1.2.4') === -1)
console.log(versionCompare('1.2', '1.2.0') === 0)
console.log(versionCompare('1.2', '1') === 1)
console.log(versionCompare('1.2.10', '1.2.1') === 1)
console.log(versionCompare('1.2.134230', '1.2.2') === 1)
console.log(versionCompare('1.2.134230', '1.3.0.1.2.3.1') === -1)
You can use a JavaScript localeCompare method:
a.localeCompare(b, undefined, { numeric: true })
Here is an example:
"1.1".localeCompare("2.1.1", undefined, { numeric: true }) => -1
"1.0.0".localeCompare("1.0", undefined, { numeric: true }) => 1
"1.0.0".localeCompare("1.0.0", undefined, { numeric: true }) => 0
// Returns true if v1 is bigger than v2, and false if otherwise.
function isNewerThan(v1, v2) {
v1=v1.split('.');
v2=v2.split('.');
for(var i = 0; i<Math.max(v1.length,v2.length); i++){
if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
if(v1[i] > v2[i]) return true;
if(v1[i] < v2[i]) return false;
}
return false; // Returns false if they are equal
}
The idea is to compare two versions and know which is the biggest. We delete "." and we compare each position of the vector with the other.
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compareVersions(a_components, b_components) {
if (a_components === b_components) {
return 0;
}
var partsNumberA = a_components.split(".");
var partsNumberB = b_components.split(".");
for (var i = 0; i < partsNumberA.length; i++) {
var valueA = parseInt(partsNumberA[i]);
var valueB = parseInt(partsNumberB[i]);
// A bigger than B
if (valueA > valueB || isNaN(valueB)) {
return 1;
}
// B bigger than A
if (valueA < valueB) {
return -1;
}
}
}
The replace() function only replaces the first occurence in the string. So, lets replace the . with ,. Afterwards delete all . and make the , to . again and parse it to float.
for(i=0; i<versions.length; i++) {
v = versions[i].replace('.', ',');
v = v.replace(/\./g, '');
versions[i] = parseFloat(v.replace(',', '.'));
}
finally, sort it:
versions.sort();
Check out this blog post. This function works for numeric version numbers.
function compVersions(strV1, strV2) {
var nRes = 0
, parts1 = strV1.split('.')
, parts2 = strV2.split('.')
, nLen = Math.max(parts1.length, parts2.length);
for (var i = 0; i < nLen; i++) {
var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
, nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;
if (isNaN(nP1)) { nP1 = 0; }
if (isNaN(nP2)) { nP2 = 0; }
if (nP1 != nP2) {
nRes = (nP1 > nP2) ? 1 : -1;
break;
}
}
return nRes;
};
compVersions('10', '10.0'); // 0
compVersions('10.1', '10.01.0'); // 0
compVersions('10.0.1', '10.0'); // 1
compVersions('10.0.1', '10.1'); // -1
If, for example, we want to check if the current jQuery version is less than 1.8, parseFloat($.ui.version) < 1.8 ) would give a wrong result if version is "1.10.1", since parseFloat("1.10.1") returns 1.1.
A string compare would also go wrong, since "1.8" < "1.10" evaluates to false.
So we need a test like this
if(versionCompare($.ui.version, "1.8") < 0){
alert("please update jQuery");
}
The following function handles this correctly:
/** Compare two dotted version strings (like '10.2.3').
* #returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
*/
function versionCompare(v1, v2) {
var v1parts = ("" + v1).split("."),
v2parts = ("" + v2).split("."),
minLength = Math.min(v1parts.length, v2parts.length),
p1, p2, i;
// Compare tuple pair-by-pair.
for(i = 0; i < minLength; i++) {
// Convert to integer if possible, because "8" > "10".
p1 = parseInt(v1parts[i], 10);
p2 = parseInt(v2parts[i], 10);
if (isNaN(p1)){ p1 = v1parts[i]; }
if (isNaN(p2)){ p2 = v2parts[i]; }
if (p1 == p2) {
continue;
}else if (p1 > p2) {
return 1;
}else if (p1 < p2) {
return -1;
}
// one operand is NaN
return NaN;
}
// The longer tuple is always considered 'greater'
if (v1parts.length === v2parts.length) {
return 0;
}
return (v1parts.length < v2parts.length) ? -1 : 1;
}
Here are some examples:
// compare dotted version strings
console.assert(versionCompare("1.8", "1.8.1") < 0);
console.assert(versionCompare("1.8.3", "1.8.1") > 0);
console.assert(versionCompare("1.8", "1.10") < 0);
console.assert(versionCompare("1.10.1", "1.10.1") === 0);
// Longer is considered 'greater'
console.assert(versionCompare("1.10.1.0", "1.10.1") > 0);
console.assert(versionCompare("1.10.1", "1.10.1.0") < 0);
// Strings pairs are accepted
console.assert(versionCompare("1.x", "1.x") === 0);
// Mixed int/string pairs return NaN
console.assert(isNaN(versionCompare("1.8", "1.x")));
//works with plain numbers
console.assert(versionCompare("4", 3) > 0);
See here for a live sample and test suite:
http://jsfiddle.net/mar10/8KjvP/
This is a neat trick. If you are dealing with numeric values, between a specific range of values, you can assign a value to each level of the version object. For instance "largestValue" is set to 0xFF here, which creates a very "IP" sort of look to your versioning.
This also handles alpha-numeric versioning (i.e. 1.2a < 1.2b)
// The version compare function
function compareVersion(data0, data1, levels) {
function getVersionHash(version) {
var value = 0;
version = version.split(".").map(function (a) {
var n = parseInt(a);
var letter = a.replace(n, "");
if (letter) {
return n + letter[0].charCodeAt() / 0xFF;
} else {
return n;
}
});
for (var i = 0; i < version.length; ++i) {
if (levels === i) break;
value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
}
return value;
};
var v1 = getVersionHash(data0);
var v2 = getVersionHash(data1);
return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
// Returns 0 or 1, correlating to input A and input B
// Direct match returns -1
var version = compareVersion("1.254.253", "1.254.253a", 3);
I made this based on Kons idea, and optimized it for Java version "1.7.0_45". It's just a function meant to convert a version string to a float. This is the function:
function parseVersionFloat(versionString) {
var versionArray = ("" + versionString)
.replace("_", ".")
.replace(/[^0-9.]/g, "")
.split("."),
sum = 0;
for (var i = 0; i < versionArray.length; ++i) {
sum += Number(versionArray[i]) / Math.pow(10, i * 3);
}
console.log(versionString + " -> " + sum);
return sum;
}
String "1.7.0_45" is converted to 1.0070000450000001 and this is good enough for a normal comparison. Error explained here: How to deal with floating point number precision in JavaScript?. If need more then 3 digits on any part you can change the divider Math.pow(10, i * 3);.
Output will look like this:
1.7.0_45 > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890 > 1.23456789
Here's a coffeescript implementation suitable for use with Array.sort inspired by other answers here:
# Returns > 0 if v1 > v2 and < 0 if v1 < v2 and 0 if v1 == v2
compareVersions = (v1, v2) ->
v1Parts = v1.split('.')
v2Parts = v2.split('.')
minLength = Math.min(v1Parts.length, v2Parts.length)
if minLength > 0
for idx in [0..minLength - 1]
diff = Number(v1Parts[idx]) - Number(v2Parts[idx])
return diff unless diff is 0
return v1Parts.length - v2Parts.length
I wrote a node module for sorting versions, you can find it here: version-sort
Features:
no limit of sequences '1.0.1.5.53.54654.114.1.154.45' works
no limit of sequence length: '1.1546515465451654654654654138754431574364321353734' works
can sort objects by version (see README)
stages (like alpha, beta, rc1, rc2)
Do not hesitate to open an issue if you need an other feature.
What is the best way of sorting if the column values are:
Before SORTING:
CANCELLED,
v06.*,
INDEPENDENT,
v06.00,
v06.00.01,
v06.01,
v06.02,
v06.00.xx,
v06.03,
v06.04,
ON HOLD,
v06.06,
v06.05,
v06.05.01,
v06.04.01,
v06.05.02,
v06.07,
v07.00,
After SORTING:
CANCELLED,
INDEPENDENT,
ON HOLD,
v06.*,
v06.00,
v06.00.01,
v06.00.xx,
v06.01,
v06.02,
v06.03,
v06.04,
v06.04.01,
v06.05,
v06.05.01,
v06.05.02,
v06.06,
v06.07,
v07.00
Thanks in advance,
Joseph
First sort it alphabetically ascending (by the 'default' sort - or sort() without passing a function), then sort it numerically. That said, I'm sure there is a better way:
function sortNumber(a, b) {
return a - b;
}
var arr = [
"v07.00", "CANCELLED", "v06.*", "v06.04.01", "INDEPENDENT", "v06.00", "v06.00.01",
"v06.01", "v06.02", "v06.00.xx", "v06.03", "v06.04", "ON HOLD",
"v06.06", "v06.05", "v06.05.01", "v06.05.02",
"v06.07",
];
alert(arr.sort().sort(sortNumber).join("\n"));
Demo: http://jsfiddle.net/karim79/rY8Du/1/
Assuming your "column values" are in an Array, use Array.sort with a custom compareFunction to define the ordering as you want.
var columnValues = [
"CANCELLED", "v06.*", "INDEPENDENT", "v06.00", "v06.00.01",
"v06.01", "v06.02", "v06.00.xx", "v06.03", "v06.04", "ON HOLD",
"v06.06", "v06.05", "v06.05.01", "v06.04.01", "v06.05.02",
"v06.07", "v07.00" ];
columnValues.sort(function(a, b) {
if (a is less than b by some ordering criterion)
return -1;
if (a is greater than b by the ordering criterion)
return 1;
// a must be equal to b
return 0;
});
Edit here's a long-winded compareFunction that seems to do what you want, at least for the example you give:
function(a, b) {
if (a==b) {
return 0;
}
if (a.length && a[0]=='v' && b.length && b[0]=='v') {
// Both strings are version strings.
// Do special case version matching.
var aParts = a.substring(1).split('.'),
bParts = b.substring(1).split('.'),
l = Math.max(a.length, b.length),
i = 0;
for (;i<l;i++) {
var aPart = aParts[i],
bPart = bParts[i];
if (aPart == '*' && bPart != '*') {
return -1;
}
if (bPart == '*' && aPart != '*') {
return 1;
}
if (aPart == 'xx' && bPart != 'xx') {
return 1;
}
if (bPart == 'xx' && aPart != 'xx') {
return -1;
}
var aNum = parseInt(aPart,10),
bNum = parseInt(bPart,10);
if (aNum < bNum) {
return -1;
}
if (aNum > bNum) {
return 1;
}
// Same so far, try next part
}
// One must be longer than the other.
return (aParts.length < bParts.length) ? -1 : 1
}
// Simple alphabetic comparison
if (a < b)
return -1;
if (a > b)
return 1;
}
Demo: http://jsfiddle.net/daybarr/h6nmg/
Once you get the column into an array you can use any natural sort method,
but you'll need to write a couple extra lines to sort the '*' the way you want.
Here is one sample-
function natSort(as, bs){
var a, b, a1, b1, i= 0, L, rx= /(\d+)|(\D+)/g, rd= /\d/;
if(isFinite(as) && isFinite(bs)) return as - bs;
a= String(as).toLowerCase();
b= String(bs).toLowerCase();
if(a=== b) return 0;
if(!(rd.test(a) && rd.test(b))) return a> b? 1: -1;
a=a.replace('*','0'); // or possibly sort any non alpha nums first:
b=b.replace('*','0'); // replace(/[^\w\.]/g,'0');
a= a.match(rx);
b= b.match(rx);
L= a.length> b.length? b.length: a.length;
while(i < L){
a1= a[i];
b1= b[i++];
if(a1!== b1){
if(isFinite(a1) && isFinite(b1)){
if(a1.charAt(0)=== "0") a1= "." + a1;
if(b1.charAt(0)=== "0") b1= "." + b1;
return a1 - b1;
}
else return a1> b1? 1: -1;
}
}
return a.length - b.length;
}
var v= "v06.05,ON HOLD,v07.00,INDEPENDENT,v06.07,v06.03,v06.*,v06.05.02,v06.05.01,"+
"v06.00.xx,v06.00,CANCELLED,v06.02,v06.04,v06.00.01,v06.06,v06.01,v06.04.01";
v=v.split(/, */);
'before sort:\n'+v.join(',\n')+'\n\nafter sort:\n'+ v.sort(natSort).join(',\n')
/* returned value:
before sort:
v06.05,
ON HOLD,
v07.00,
INDEPENDENT,
v06.07,
v06.03,
v06.*,
v06.05.02,
v06.05.01,
v06.00.xx,
v06.00,
CANCELLED,
v06.02,
v06.04,
v06.00.01,
v06.06,
v06.01,
v06.04.01
after sort:
CANCELLED,
INDEPENDENT,
ON HOLD,
v06.*,
v06.00,
v06.00.01,
v06.00.xx,
v06.01,
v06.02,
v06.03,
v06.04,
v06.04.01,
v06.05,
v06.05.01,
v06.05.02,
v06.06,
v06.07,
v07.00
*/