52

Given an array of objects:

{
    key: "a",
    value: 42
},
{
    key: "d",
    value: 28
},
{
    key: "c",
    value: 92
},
{
    key: "b",
    value: 87
}

and an array of keys:

["c", "a", "b", "d"]

Is there a ECMAScript function or a 3rd-party JavaScript library that lets you sort - in one line/function call - the first array of objects, to match the order of the keys specified in the second array, such that the result is:

{
    key: "c",
    value: 92
},
{
    key: "a",
    value: 42
},
{
    key: "b",
    value: 87
},
{
    key: "d",
    value: 28
}

Other questions that provide a function or algorithm:

Similar/related questions:

5
  • Have you tried using a combination of array.sort(function()) and insertion sort/selection sort? or you just need a third party solution? Commented Sep 17, 2013 at 20:27
  • I found a couple of other questions with solutions after posting this, and updated the question. So now the goal is to find something that provides it out of the box, rather than re-implementing it. Commented Sep 17, 2013 at 20:30
  • Is there a ECMAScript function or a 3rd-party JavaScript library that lets you... is a shopping question and will be closed as such. Commented Sep 17, 2013 at 20:32
  • I am baffled as to why this question is being voted for closure. Commented Sep 17, 2013 at 20:32
  • if there were a native way to do it, someone would have mentioned it in one of the other questions. Commented Sep 17, 2013 at 20:37

6 Answers 6

73

Just use indexOf to convert the key to the correct order:

var order = ["c", "a", "b", "d"];
_.sortBy(arr, function(obj){ 
    return _.indexOf(order, obj.key);
});

Fiddle

If there are a lot of keys, then it would be advantageous to make a hash-map out of the array, like:

var order = ["c", "a", "b", "d"];
var orderMap = {};
_.each(order, function(i) { orderMap[i] = _.indexOf(order, i); });

This makes the key-sorting lookup constant time rather than O(n). (Fiddle)

Sign up to request clarification or add additional context in comments.

6 Comments

Awesome answer. So simple.
I like this. It would still be nice to find it in an existing library is a single function. I'm using CoffeeScript, so this actually makes a nice one-liner: arr = _.sortBy arr, (obj) -> _.indexOf order, obj.key.
@mkopala you can add your own functions to underscore using _.mixin() underscorejs.org/#mixin
This example was not really clear for me. Here is an additional example that made it clear for me, jsfiddle.net/vsn32xp3.
Here is another version which supports arranging array items that are not included in the order array to be re-ordered at the back of the sorted array. In your version, items that are not included in the order array are placed at the front of the sorted array.
|
40

Great answers provided so far. Thought that the following may also be an alternative solution in plain JS:

var arr = arr.sort(function(a,b) {
    return order.indexOf( a.key ) - order.indexOf( b.key );
    //for the sake of recent versions of Google Chrome use:
    //return a.key.charCodeAt(0) > b.key.charCodeAt(0); or return a.key.charCodeAt(0) - b.key.charCodeAt(0);
});

var arr = [
    {
        key: "a",
        value: 42
    },
    {
        key: "d",
        value: 28
    },
    {
        key: "c",
        value: 92
    },
    {
        key: "b",
        value: 87
    }
];

var order = ["c", "a", "b", "d"];

console.log( 'Original: ', JSON.stringify( arr ) );

var arr = arr.sort(function(a,b) {
      return order.indexOf( a.key ) - order.indexOf( b.key );
});

console.log( 'Ordered: ', JSON.stringify( arr ) );

5 Comments

In Chrome 60, I am unable to return a boolean directly from the sort callback, and I need to instead return one of -1, 0 or 1.
@BardiHarborow, how about return a.key.charCodeAt(0) > b.key.charCodeAt(0); or return a.key.charCodeAt(0) - b.key.charCodeAt(0);?
I am pretty sure this should be order.indexOf( a.key ) - order.indexOf( b.key ) instead of a boolean comparison operator.
Great answer, thx.
I'm assuming your order variable refers to var order = ["c", "a", "b", "d"]; ?
20

const obj = [
    {
        key: "a",
        value: 42
    },
    {
        key: "d",
        value: 28
    },
    {
        key: "c",
        value: 92
    },
    {
        key: "b",
        value: 87
    }
]


const sortList = ["c", "a", "b", "d"];
    
    
const sortedObj = obj.sort((a, b) => {
    return (
        sortList.indexOf(a.key) - sortList.indexOf(b.key)
    );
});

console.log(sortedObj );

Comments

3

I can't claim that this is the most efficient way, but you can use the key for each object as a key for properties in another object. Then simply access them by these keys.

for (x = 0; x < objn.length; x++) {
    newobj[objn[x].key] = objn[x];
}
objn = [];
for (x = 0; x < keys.length; x++) {
    objn.push(newobj[keys[x]]);
}
console.log(objn);

http://jsfiddle.net/WdehF/

Comments

2
const data = [{key:"a"},{key:"d"},{key:"c"},{key:"b"}]      // <-your data
const order = ["c", "a", "b", "d"]          // <-create an array in the order you wish
const orderedArray = order.map(char=>data.find(res=>res.key===char))     // <- what you want

For each char in order: it will map if the char is equal to any key found within your data, and return it, consecutively

Comments

1
// create hash map el.key -> index, to help us with direct access, avoid searching
const hashMap = arr.reduce((acc, el, index) => { acc[el.id] = el; return acc }, {})

// finally, map the ids to the final result
const ids.map(id => hashMap[id])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.