Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort an array by the contents of another in JavaScript

Say I have an array of Person objects:

var people = [{name: "Joe Schmo", age: 36}, {name: "JANE DOE", age: 40}];

and I have a function which can sort an array of strings case insensitively:

function caseInsensitiveSort(arr) { ... }

Is there any straightforward way to combine my existing sort function with Array.prototype.map to sort the people array just using the name key?

I.e. it would produce

var people = [{name: "JANE DOE", age: 40}, {name: "Joe Schmo", age: 36}];

Doing it by hand is not hard in this particular case,

people.sort(function (a, b) {
    return a.name.localeCompare(b.name);
});

but I can't think of a way of doing it that would allow me to use the pre-existing sort function. In a case where the sort function is more customized, that would be useful.

Edit: I believe that the core problem here is that to do this well, you need to be able to figure out what the original indices were mapped to when you sort the proxy array. Getting those new indices using JS's native sort function does not seem possible in the general case. But I'd be happy to be proven wrong.

Edit: The way I was trying to do this is too inefficient to be useful. See the answer below for the solution using a comparison function instead.

like image 932
Chris Middleton Avatar asked Apr 17 '15 16:04

Chris Middleton


2 Answers

You could use your existing function to get a sorted names array, then sort the people array by comparing the index in the sorted names array.

var names = caseInsensitiveSort(people.map(function(person) { 
    return person.name;
}));

people.sort(function (a, b) {
    return names.indexOf(a.name) - names.indexOf(b.name);
});

But this is not efficient, you should try to abstract out the compare logic from the caseInsensitiveSort function into a caseInsensitiveCompare function.

Then your example would become:

people.sort(function (a, b) {
    return caseInsensitiveCompare(a.name, b.name);
});
like image 148
xdazz Avatar answered Nov 13 '22 05:11

xdazz


Depending on how your caseInsensitiveSort() function works, you could make use of a .toString() method to do this:

var toSort = people.map(function (person) {
    return {
        person: person,
        toString: function () {
            return person.name;
        }
    };
});

caseInsensitiveSort(toSort);

people = toSort.map(function (item) { return item.person; });

If that is not an option, a messier yet still efficient approach is to sort the names, map them to their indices, and then sort based on that:

var names = people.map(function (person) { return person.name; });

caseInsensitiveSort(names);

var nameMap = {};
names.forEach(function (name, i) { 
    nameMap[name] = i;
});

people.sort(function (a, b) {
    return nameMap[a.name] - nameMap[b.name];
});
like image 43
JLRishe Avatar answered Nov 13 '22 06:11

JLRishe