Sort version-dotted number strings in Javascript?

You could prepend all parts to fixed size strings, then sort that, and finally remove the padding again.

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
         .map( a => a.split('.').map( n => +n-100000 ).join('.') );

console.log(arr)

Obviously you have to choose the size of the number 100000 wisely: it should have at least one more digit than your largest number part will ever have.

With regular expression

The same manipulation can be achieved without having to split & join, when you use the callback argument to the replace method:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.replace(/\d+/g, n => +n+100000 ) ).sort()
         .map( a => a.replace(/\d+/g, n => +n-100000 ) );

console.log(arr)

Defining the padding function once only

As both the padding and its reverse functions are so similar, it seemed a nice exercise to use one function f for both, with an extra argument defining the “direction” (1=padding, -1=unpadding). This resulted in this quite obscure, and extreme code. Consider this just for fun, not for real use:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = (f=>f(f(arr,1).sort(),-1)) ((arr,v)=>arr.map(a=>a.replace(/\d+/g,n=>+n+v*100000)));

console.log(arr);

Use the sort compare callback function

You could use the compare function argument of sort to achieve the same:

arr.sort( (a, b) => a.replace(/\d+/g, n => +n+100000 )
                     .localeCompare(b.replace(/\d+/g, n => +n+100000 )) );

But for larger arrays this will lead to slower performance. This is because the sorting algorithm will often need to compare a certain value several times, each time with a different value from the array. This means that the padding will have to be executed multiple times for the same number. For this reason, it will be faster for larger arrays to first apply the padding in the whole array, then use the standard sort, and then remove the padding again.

But for shorter arrays, this approach might still be the fastest. In that case, the so-called natural sort option — that can be achieved with the extra arguments of localeCompare — will be more efficient than the padding method:

var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.sort( (a, b) => a.localeCompare(b, undefined, { numeric:true }) );

console.log(arr);

More about the padding and unary plus

To see how the padding works, look at the intermediate result it generates:

[ "100005.100005.100001", "100004.100021.100000", "100004.100022.100000", 
  "100006.100001.100000", "100005.100001.100000" ]

Concerning the expression +n+100000, note that the first + is the unary plus and is the most efficient way to convert a string-encoded decimal number to its numerical equivalent. The 100000 is added to make the number have a fixed number of digits. Of course, it could just as well be 200000 or 300000. Note that this addition does not change the order the numbers will have when they would be sorted numerically.

The above is just one way to pad a string. See this Q&A for some other alternatives.

Leave a Comment