15

I have a list of objects:

[ { id: 4, name:'alex' }, { id: 3, name:'jess' }, { id: 9, name:'...' }, { id: 1, name:'abc' } ]

I have another list with the right "order".

[ 3, 1, 9, 4]

How can I match the first list to the ordering of the second list, based on the key "id"? The result should be:

[ { id: 3, name:'jess' }, { id: 1, name:'abc' }, { id: 9, name:'...' }, { id: 4, name:'alex' } ]
TIMEX
  • 259,804
  • 351
  • 777
  • 1,080

9 Answers9

30

I stepped in this problem and solved it with a simple .sort

Assuming that your list to be sorted is stored in the variable needSort and the list with the order is in the variable order and the both are in the same scope you can run a .sort like this:

needSort.sort(function(a,b){
  return order.indexOf(a.id) - order.indexOf(b.id);
});

It worked for me, hope it helps.

georg
  • 211,518
  • 52
  • 313
  • 390
HClx
  • 461
  • 4
  • 5
  • 2
    Your 3 lines can be condensed: `return order.indexOf(a.id) < order.indexOf(b.id) ? -1 : 1;` JSFiddle: http://jsfiddle.net/zamnuts/guyjm0za/ – zamnuts Feb 20 '15 at 18:37
  • Thanks for the suggestion, I've added it to the answer. – HClx Feb 23 '15 at 19:00
  • Hey, will this work, if the length of both arrays i.e. the actual array to be sorted and order array are different? And also, in that case, if we need to further sort it by id, in case 2 id's are same, how can we do that. – whyAto8 Jul 13 '16 at 09:33
  • 2
    You are committing a crime by not mark this as answer. – VHanded Mar 29 '17 at 15:06
  • 2
    I think this answer has a problem, because the sort function does not return `0` if two indices are equal. It's pathological, because that would happen only if two elements in the needSort have the same `id`. Therefore I would use `return order.indexOf(a.id) - order.indexOf(b.id);` – Michael_Scharf Sep 13 '17 at 21:29
  • nice in my case had to amend both to strings and worked perfectly: `return order.indexOf('' + a.id) - order.indexOf('' + b.id);` – Ylama Dec 23 '19 at 07:17
10

How I solved pretty much the same issue

data = [{ id: 4, name:'alex' }, { id: 3, name:'jess' }, { id: 9, name:'...' }, { id: 1, name:'abc' } ];

sorted = [3, 1, 9, 4].map((i) => data.find((o) => o.id === i));
Frans
  • 1,389
  • 2
  • 16
  • 28
  • This code assumes that there is an element in `[3, 1, 9, 4]` for each element in `data`. If data contains only a subset, then the sorted list may contain `undefined` elements. – Michael_Scharf Sep 13 '17 at 21:39
  • True. Easily fixable by appending something like `.filter(o => o)` though, depending on what you're looking for. – Frans Sep 20 '17 at 11:35
7

Well, the simple answer would be, "for a set of data this small, anything less costly than an infinite loop will be basically unnoticeable." But let's try to answer this "right."

There's no rhyme or reason to the order in the second array, it's just a list of foreign keys (to use SQL terminology) on the primary keys of the first array. So, thinking of them as keys, and that we want efficient lookup of those keys, a hash table (object) would probably "sort" this the quickest, in an O(n) fashion (2*n, really) assuming the first array is called objArray and the second array is called keyArray:

// Create a temporary hash table to store the objects
var tempObj = {};
// Key each object by their respective id values
for(var i = 0; i < objArray.length; i++) {
    tempObj[objArray[i].id] = objArray[i];
}
// Rebuild the objArray based on the order listed in the keyArray
for(var i = 0; i < keyArray.length; i++) {
    objArray[i] = tempObj[keyArray[i]];
}
// Remove the temporary object (can't ``delete``)
tempObj = undefined;

And that should do it. I can't think of any method that doesn't require two passes. (Either one after the other, like this, or by passing multiple times through the array and spliceing out the found elements, which can get costly with backwards-sorted data, for instance.)

  • You could speed this up further by moving the objArray.length to a var so it doesn't get evaluated every time. (but you already know that:) this gets +1 from me – Aran Mulholland Jul 11 '13 at 04:21
1

I think the best way you'll find is to put all the elements of the first list into a hash using the id values as the property name; then build the second list by iterating over the list of ids, looking up each object in the hash, and appending it to the list.

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
1

Make the list into a object so instead of order = [3, 1, 9, 4] you will have order = { 3:0, 1:1, 9:2, 4:3}, then do the following

function ( order, objects ){
     ordered_objects = []
     for( var i in objects ){
           object = objects[i]
           ordered_objects[ order[ object.id ] ] = object
     }
     return ordered_objects
}
Doboy
  • 10,411
  • 11
  • 40
  • 48
1

A little something like this:

var data = [ { id: 4, name:'alex' }, { id: 3, name:'jess' }, { id: 9, name:'...' }, { id: 1, name:'abc' } ],
    order = [ 3, 1, 9, 4],    
    sorted = [],    
    items = {},
    i;

for (i = 0; i < data.length; i++)
   items[data[i].id] = data[i];

for (i = 0; i < order.length; i++)
   sorted.push(items[order[i]]);

The idea is to put the items from data into an object using the ids as property names - that way you can retrieve the item with a given id without without having to search through the array. (Otherwise you'd have to use a nested loop, or the Array.indexOf() function inside a single loop which is effectively going to be a nested loop as far as performance.)

This assumes that no two elements in data have the same id property.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
1

DEMO

function sort(array, order) {

    //create a new array for storage
    var newArray = [];

    //loop through order to find a matching id
    for (var i = 0; i < order.length; i++) { 

        //label the inner loop so we can break to it when match found
        dance:
        for (var j = 0; j < array.length; j++) {

            //if we find a match, add it to the storage
            //remove the old item so we don't have to loop long nextime
            //and break since we don't need to find anything after a match
            if (array[j].id === order[i]) {
                newArray.push(array[j]);
                array.splice(j,1);
                break dance;
            }
        }
    }
    return newArray;
}

var newOrder = sort(oldArray,[3, 1, 9, 4]);
console.log(newOrder);​
Joseph
  • 117,725
  • 30
  • 181
  • 234
  • Assuming there are no repeated ids you could speed this up if you put a `break` inside the `if` block. – nnnnnn Mar 18 '12 at 04:20
0

You can do it with Alasql library with simple SELECT JOIN of two arrays.

The only one thing: Alasql understands source data as array of arrays or array of objects, so you need to convert simple array to array of arrays (see Step 1)

var data1 = [ { id: 3, name:'jess' }, { id: 1, name:'abc' }, 
   { id: 9, name:'...' }, { id: 4, name:'alex' } ];
var data2 = [3, 1, 9, 4];

// Step 1: Convert [3,1,9,4] to [[3],[1],[9],[4]]
var data2a = data2.map(function(d){return [d]});

// Step 2: Get the answer
var res = alasql('SELECT data1.* FROM ? data1 JOIN ? data2 ON data1.id = data2.[0]',
    [data1,data2a]);

Try this example at jsFiddle.

agershun
  • 4,077
  • 38
  • 41
0
const obj = [ { id: 4, name:'alex' }, { id: 3, name:'jess' }, { id: 9, name:'...' }, { id: 1, name:'abc' } ];
const id = [ 3, 1, 9, 4];
const result = id.map(i => obj.find(j => j.id === i));
Pacuraru Daniel
  • 1,207
  • 9
  • 30
  • 56