Pavan
Pavan

Reputation: 668

Sort an array of strings by two or more substrings in javascript

I have a javascript array which looks like this:

var data =  ["size64_tolPercent0.01_best", "size256_tolPercent0.01_best", "size256_tolPercent0_worst", "sizemax_tolPercent0_worst", "size1518_tolPercent0_worst", "size64_tolPercent0.01_worst", "size512_tolPercent0.01_worst", "size1518_tolPercent0.01_worst", "size256_tolPercent0_best"] etc

I want to sort this array :

  1. by the substring "best" and "worst".
  2. by the tolPercents 0 and 0.01
  3. sizes (64, 256, 512, 1518, max).. it would be good if the sizes are sorted by strings since it contains a "max" keyword..

So, we end up with a result array which looks like this:

["size64_tolPercent0_best", "size256_tolPercent0_best", "size512_tolPercent0_best", "size1518_tolPercent0_best", "sizemax_tolPercent0_best", "size64_tolPercent0.01_best", "size512_tolPercent0.01_best", "size1518_tolPercent0.01_best", "size64_tolPercent0_worst", "size256_tolPercent0_worst", "size512_tolPercent0_worst", "size1518_tolPercent0_worst", "sizemax_tolPercent0_worst"] etc

I am able to sort the strings using one of the methods but not all.

Here's what I've tried so far, I'm doing it wrong I'm sure, just need some help in the right direction. Doing it for 1 and 3 now:

var someLargeValue = 10000000;
data.sort(function(x,y){
  var xp = x.substr(getPosition(x, '_', 2) + 1, x.split("_").pop().length);
  var yp = y.substr(getPosition(y, '_', 2) + 1, y.split("_").pop().length);
  return xp == yp ? 0 : xp < yp ? -1 : 1;
});
data.sort(function(x,y){
  var xp = x.substr(4, getPosition(x, '_', 1) - 4);
  var yp = y.substr(4, getPosition(y, '_', 1) - 4);
  if(xp === "max") {
    xp = someLargeValue;
  }
  if(yp === "max") {
    yp = someLargeValue;
  }
  xp = parseInt(xp);
  yp = parseInt(yp);

  return xp == yp ? 0 : xp < yp ? -1 : 1;
});
function getPosition(str, m, i) {
    return str.split(m, i).join(m).length;
}

But i'm afraid the code i'm trying is doing the sorting sequentially.. so what the first custom sort method is overridden by the second, I think?

Any help much appreciated.

Upvotes: 1

Views: 1039

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386600

This solution features the Sorting with map from MDN.

First build a mapped array with splitted items for sorting. Then sort it by the custom order.

function (a, b) {
    return a.rating.localeCompare(b.rating) ||
           a.tolPercents - b.tolPercents ||
           a.size - b.size;
}

Altogether:

var data = [
        "size64_tolPercent0.01_best",
        "size256_tolPercent0.01_best",
        "size256_tolPercent0_worst",
        "sizemax_tolPercent0_worst",
        "size1518_tolPercent0_worst",
        "size64_tolPercent0.01_worst",
        "size512_tolPercent0.01_worst",
        "size1518_tolPercent0.01_worst",
        "size256_tolPercent0_best"
    ],
    result = data.map(function (el, i) {
        var a = /^size(.+)_tolpercent(.+)_(.+)$/i.exec(el);
        return {
            index: i,
            rating: a[3],
            tolPercents: a[2],
            size: a[1] === 'max' ? Number.MAX_VALUE : a[1]
        };
    }).sort(function (a, b) {
        return a.rating.localeCompare(b.rating) || a.tolPercents - b.tolPercents || a.size - b.size;
    }).map(function (el) {
        return data[el.index];
    });

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');

Upvotes: 2

user1636522
user1636522

Reputation:

I would first split the strings in order to minimize the amount of work done by the comparison function, as well as make it more readable. Then, I would recreate the strings once the array is sorted. Using a map is smarter though: https://stackoverflow.com/a/32379540/1636522 :-D

var data = ["size64_tolPercent0_best", "size256_tolPercent0_best", "size512_tolPercent0_best", "size1518_tolPercent0_best", "sizemax_tolPercent0_best", "size64_tolPercent0.01_best", "size512_tolPercent0.01_best", "size1518_tolPercent0.01_best", "size64_tolPercent0_worst", "size256_tolPercent0_worst", "size512_tolPercent0_worst", "size1518_tolPercent0_worst", "sizemax_tolPercent0_worst"];

// split the strings

var re = /max|\d+(?:\.\d+)?(?=_)|[^_]+$/g;

data = data.map(function (x) {
  x = x.match(re); // example: ["64", "0.01", "best"]
  if (x[0] !== 'max') x[0] = parseInt(x[0], 10);
  x[1] = parseFloat(x[1]);
  return x;
});

// sort the array

data.sort(function cmp (a, b) {
  // 1. "best" and "worst"
  if (a[2] > b[2]) return 1;
  if (a[2] < b[2]) return -1;
  // 2. percent
  if (a[1] > b[1]) return 1;
  if (a[1] < b[1]) return -1;
  // 3. size
  if (a[0] === 'max') return 1;
  if (b[0] === 'max') return -1;
  if (a[0] > b[0]) return 1;
  if (a[0] < b[0]) return -1;
  // 4. no differences
  return 0;
});

// recreate the strings

data = data.map(function (x) {
  x[0] = 'size' + x[0];
  x[1] = 'tolPercent' + x[1];
  return x.join('_');
});

// print the result

data.forEach(function (x) {
  document.write(x + '<br />');
});
* {font-family:Courier}

Upvotes: 2

Mathew Grabau
Mathew Grabau

Reputation: 74

The Array class has a sort function: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

Leverage the compare argument:

data.sort(function(a, b) { // Follow the docs as per how a relates to b, returning -1 (a < b),0 (equal) or 1 (a > b) });

Extract and compare the sub strings as required in the function.

Update
After reading your code that you added, don't call sort() twice, just incorporate all the comparisons into the same sort comparison. Stop at the first inequality (aValue !== bValue) and return that comparison.

Upvotes: -1

Related Questions