user3921420 user3921420 - 23 days ago 6
Javascript Question

Sort version-dotted number strings in Javascript?

I have an array of following strings:

['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']


...etc.

I need a solution that will give me following ordered result

['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0'].


I tried to implement a sort so it first sorts by the numbers in the first position, than in case of equality, sort by the numbers in the second position (after the first dot), and so on...

I tried using sort() and localeCompare(), but if I have elements
'4.5.0'
and
'4.11.0'
, I get them sorted as
['4.11.0','4.5.0']
, but I need to get
['4.5.0','4.11.0']
.

How can I achieve this?

Answer

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);

Remarks

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.

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 unitary 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.