Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A more elegant secondary sort for arrays [closed]

I need to perform a sort on an array and if two elements are equal I then need to perform a secondary sort on a different key within those elements. Having a look at the Mozilla Developer Network docs for array.sort there is a nice snippet at code at the bottom to handle the first sort. I liked it because it was succinct and shows how you can write powerful JS.

Here is what I tried based on the code from MDN. This correctly does the first sort.

// the array to be sorted
var list = [{name:'Delta', ref: 456}, {name:'Delta', ref: 123}, {name:'alpha', ref: 789}, {name:'CHARLIE', ref: 012}, {name:'bravo', ref: 345}];

// temporary array holds objects with position and sort-value
var mapped = list.map(function (el, i) {
  return {
    index: i,
    value: el.name.toLowerCase(),
    secondaryValue: el.ref
  };
});

// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
  return +(a.value > b.value) || +(a.value === b.value) - 1;
});

// container for the resulting order
var result = mapped.map(function (el) {
  return list[el.index];
});

console.log(list);
console.log(result);

Now I know I can modify the comparison function like this to complete the requirement of the secondary sort:

// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
  if (a.value === b.value){
    return +(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1;
  }

  return +(a.value > b.value) || - 1;
});

But, to me, this loses some of the charm of return +(a.value > b.value) || +(a.value === b.value) - 1; - which is pretty cool in my opinion.

Question: Is there a more elegant way to perform the secondary sort?

Restriction: Pure JS only. ES5 compatible preferably but interested to hear if ES6 can help.

like image 370
mez.pahlan Avatar asked Dec 03 '15 20:12

mez.pahlan


People also ask

How do you sort an array based on the second column in Python?

Use the syntax array[:, j – 1] to extract the j -th column of an array. Call numpy. argsort(a) to return the sorted indices of the column a . Then use these sorted indices to sort the rows of the same array by column a .

How do you sort arrays from lowest to highest?

The sort() method allows you to sort elements of an array in place. Besides returning the sorted array, the sort() method changes the positions of the elements in the original array. By default, the sort() method sorts the array elements in ascending order with the smallest value first and largest value last.


2 Answers

You can chain the sort comparison with a logic or ||, because same values are evaluated to 0 and that is falsy so the next part is evaluated.

mapped.sort(function (a, b) {
    return (+(a.value > b.value) || +(a.value === b.value) - 1) ||
        (+(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1);
});

Or use more compact version of the above comparison

var list = [{name:'Delta', ref: 456}, {name:'Delta', ref: 123}, {name:'alpha', ref: 789}, {name:'CHARLIE', ref: 12}, {name:'bravo', ref: 345}],
    mapped = list.map(function (el, i) {
        return {
            index: i,
            value: el.name.toLowerCase(),
            secondaryValue: el.ref
        };
    });

mapped.sort(function (a, b) {
    return a.value.localeCompare(b.value) || a.secondaryValue - b.secondaryValue;
});

var result = mapped.map(function (el) {
    return list[el.index];
});

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
like image 165
Nina Scholz Avatar answered Sep 28 '22 02:09

Nina Scholz


I'm on the fence about answering this so I'm going to answer the question of:

How can an array be concisely sorted by multiple sorting functions?

The question isn't exactly what you're asking, but this way it's a little better defined and answerable.


Sorting an array with a single function is pretty straightforward using Array.prototype.sort, but unfortunately wasn't forward-thinking enough to allow for multiple parameters. It also has the problem of modifying the array directly rather than being chainable:

a = [2, 1, 3];
a.sort(function (a, b) {
    return b - a;
});

Sorting by multiple dimensions is trickier because all dimensions need to be accounted for in the sort callback, or you need to write your own sorting algorithm, which is irksome.

If you're using underscore or lodash, there are various sorting utilities available which are convenient, but if you're not using another library a multisort function isn't overly difficult, you can feel free to use the one i whipped up quickly which I haven't hardly tested:

multisort = (function () {
// or for commonjs
// module.exports = (function () {
    function slice(arr) {
        return Array.prototype.slice.call(arr);
    }

    // multisort(arr, [fn1], [fn2] ...)
    return function multisort(arr) {
        var args;

        //copy the arguments into a new array
        args = slice(arguments);

        //copy the array to a new array and sort the new array    
        return slice(arr).sort(function (a, b) {
            var i,
                fn,
                res;

            //iterate over the sort callbacks
            for (i = 1; i < args.length; i++) {
                fn = args[i];
                //call the sort callback,
                //this version doesn't do anything special with setting a context
                res = fn(a, b);
                //if the sort value is anything other than 0, return it
                //otherwise fall to the next sort function
                if (res) {
                    return res;
                }
            }
            return 0;
        });
    }
}());

You'd use it as:

mapped = multisort(mapped, function (a, b) {
    return +(a.value > b.value) || +(a.value === b.value) - 1;
}, function (a, b) {
    return +(a.secondaryValue > b.secondaryValue) || +(a.secondaryValue === b.secondaryValue) - 1;
});

This format keeps the sorting functions distinct from one-another, so that they can be mixed and matched in whatever order you'd prefer.

like image 21
zzzzBov Avatar answered Sep 28 '22 03:09

zzzzBov