48

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:

Community
  • 1
  • 1
mkopala
  • 1,262
  • 3
  • 12
  • 15
  • Have you tried using a combination of array.sort(function()) and insertion sort/selection sort? or you just need a third party solution? – neoeahit Sep 17 '13 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. – mkopala Sep 17 '13 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. – Evan Davis Sep 17 '13 at 20:32
  • I am baffled as to why this question is being voted for closure. – jessegavin Sep 17 '13 at 20:32
  • if there were a native way to do it, someone would have mentioned it in one of the other questions. – lincolnk Sep 17 '13 at 20:37

6 Answers6

71

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)

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Awesome answer. So simple. – jessegavin Sep 17 '13 at 20:31
  • 1
    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 Sep 17 '13 at 20:56
  • 2
    @mkopala you can add your own functions to underscore using _.mixin() http://underscorejs.org/#mixin – jessegavin Sep 23 '13 at 19:36
  • 3
    This example was not really clear for me. Here is an additional example that made it clear for me, http://jsfiddle.net/vsn32xp3/. – Gajus Nov 20 '15 at 14:55
  • Here is [another version](http://jsfiddle.net/sTwVD/41/) 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. – Raptor Feb 17 '17 at 10:03
  • The `orderMap` can also be created directly with `_.invert(order)`. – Julian Apr 29 '21 at 11:02
37

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 ) );
Cullub
  • 2,901
  • 3
  • 30
  • 47
PeterKA
  • 24,158
  • 5
  • 26
  • 48
18

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 );
user1462498
  • 354
  • 1
  • 4
  • 10
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/

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
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])
Josmar
  • 584
  • 7
  • 16
1
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

Naruto Uzumaki
  • 442
  • 4
  • 7