Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 677
user3921420 Avatar asked Oct 23 '16 09:10

user3921420


People also ask

How do you sort semantic versioning?

If you find yourself having to sort using semantic version strings, (sorting a list of releases chronologically, say), we first need to split the string into its values and then compare each value. In this case we would sort by major release first, then minor, then finally by patch number.

Can JavaScript sort strings containing numbers?

Voted Best Answer. You should be using the JavaScript array object's sort method. If you are going to sort strings that consist of letters and numbers you may need to pad some numbers with leading zeros so the numeric portion of the string is the same length for all members.

Can you sort a string in JavaScript?

JavaScript Array sort() The sort() sorts the elements as strings in alphabetical and ascending order.


4 Answers

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.

like image 91
trincot Avatar answered Oct 19 '22 16:10

trincot


If you are looking for a npm package to compare two semver version, https://www.npmjs.com/package/compare-versions is the one.

Then you can sort version like this:

// ES6/TypeScript
import compareVersions from 'compare-versions';

var versions = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var sorted = versions.sort(compareVersions);
like image 41
Tim Yao Avatar answered Oct 19 '22 15:10

Tim Yao


You could split the strings and compare the parts.

function customSort(data, order) {

  function isNumber(v) {
    return (+v).toString() === v;
  }

  var sort = {
    asc: function (a, b) {
      var i = 0,
        l = Math.min(a.value.length, b.value.length);

      while (i < l && a.value[i] === b.value[i]) {
        i++;
      }
      if (i === l) {
        return a.value.length - b.value.length;
      }
      if (isNumber(a.value[i]) && isNumber(b.value[i])) {
        return a.value[i] - b.value[i];
      }
      return a.value[i].localeCompare(b.value[i]);
    },
    desc: function (a, b) {
      return sort.asc(b, a);
    }
  }
  var mapped = data.map(function (el, i) {
    return {
      index: i,
      value: el.split('')
    };
  });

  mapped.sort(sort[order] || sort.asc);
  return mapped.map(function (el) {
    return data[el.index];
  });
}

var array = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0'];

console.log('sorted array asc', customSort(array));
console.log('sorted array desc ', customSort(array, 'desc'));
console.log('original array ', array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 5
Nina Scholz Avatar answered Oct 19 '22 17:10

Nina Scholz


You can check in loop if values are different, return difference, else continue

var a=['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];

a.sort(function(a,b){
  var a1 = a.split('.');
  var b1 = b.split('.');
  var len = Math.max(a1.length, b1.length);
  
  for(var i = 0; i< len; i++){
    var _a = +a1[i] || 0;
    var _b = +b1[i] || 0;
    if(_a === _b) continue;
    else return _a > _b ? 1 : -1
  }
  return 0;
})

console.log(a)
like image 2
Rajesh Avatar answered Oct 19 '22 17:10

Rajesh