简体   繁体   中英

Sort an array of objects based on another array of ids

I have 2 arrays

a = [2,3,1,4]
b = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]

How do I get b sorted based on a ? My desired output would be

c = [{id: 2}, {id: 3}, {id: 1}, {id: 4}]

I would prefer to use Ramda or regular JS.

You can provide a custom comparison function to JavaScript's Array#sort method.

Use the custom comparison function to ensure the sort order:

var sortOrder = [2,3,1,4],
    items     = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];

items.sort(function (a, b) {
  return sortOrder.indexOf(a.id) - sortOrder.indexOf(b.id);
});

MDN :

  • If compareFunction(a, b) returns less than 0, sort a to an index lower than b (ie a comes first).
  • If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behavior, thus, not all browsers (eg Mozilla versions dating back to at least 2003) respect this.
  • If compareFunction(a, b) returns greater than 0, sort b to an index lower than a (ie b comes first).

Hitmands made a very fair comment on the above solution:

This approach is O(n2), and would cause performance issues in big sized lists. Better to buiild the dictionary first, so that it stays O(n)

Consequently, the above solution might end up not being as fast as you need on large inputs.

To implement Hitmands' suggestion:

let sortOrder = [2,3,1,4],
    items     = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];

const itemPositions = {};
for (const [index, id] of sortOrder.entries()) {
  itemPositions[id] = index;
}

items.sort((a, b) => itemPositions[a.id] - itemPositions[b.id]);

Ramda really shines for these types of problems.

Where the size of the data is small, we can use a simple reduce function, and indexOf helper.

// match id of object to required index and insert
var sortInsert = function (acc, cur) {
  var toIdx = R.indexOf(cur.id, a);
  acc[toIdx] = cur;
  return acc;
};

// point-free sort function created
var sort = R.reduce(sortInsert, []);

// execute it now, or later as required
sort(b);
// [ { id: 2 }, { id: 3 }, { id: 1 }, { id: 4 } ]

This works well for small(ish) data sets but the indexOf operation on every iteration through the reduction is inefficient for large data sets.

We can remedy this by tackling the problem from the other side, lets use groupBy to group our objects by their id, thus creating a dictionary lookup (much better!). We can then simply map over the required indexes and transform them to their corresponding object at that position.

And here is the solution using this approach:

var groupById = R.groupBy(R.prop('id'), b);

var sort = R.map(function (id) {
    return groupById[id][0];
});

sort(a);
// [ { id: 2 }, { id: 3 }, { id: 1 }, { id: 4 } ]

Finally, this is yet another solution, which is very succinct:

R.sortBy(R.pipe(R.prop('id'), R.indexOf(R.__, a)))(b);
// [ { id: 2 }, { id: 3 }, { id: 1 }, { id: 4 } ]

I love the way you can compose functions with behaviour and separate the algorithm from the data upon which it acts using Ramda . You end up with very readable, and easy to maintain code.

Using ES6 map and find

const a = [2,3,1,4];
const b = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];

// map over a, find it in b and return    
const c = a.map((i) => b.find((j) => j.id === i));

You could just create c based off of a without ever using b :

var a = [2,3,1,4];
var c = [];

for(var i = 0; i < a.length; i++){

    c.append({id:a[i]);

}

Hope this helps!

This solution uses Array#sort with a helper object c for the indices.

{
    "1": 2,
    "2": 0,
    "3": 1,
    "4": 3
}

 var a = [2, 3, 1, 4], b = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }], c = a.reduce(function (r, a, i) { r[a] = i; return r; }, {}); b.sort(function (x, y) { return c[x.id] - c[y.id]; }); document.write('<pre>' + JSON.stringify(b, 0, 4) + '</pre>');

For greater objects, I suggest to use Sorting with map .

Or may be simpler

b.sort(function(obj1,obj2){
   return a.indexOf(obj1.id) > a.indexOf(obj2.id)
});

Using Ramda, you have to map objects in b to their index via the mapObjIndexed function and then search the value in a . You can try it here .

var a = [2,3,1,4];
var b =  [{id: 1}, {id: 2}, {id: 3}, {id: 4}]
R.find( R.propEq('id', a[0]), b )
R.values(R.mapObjIndexed(  (num, key, obj) => R.find( R.propEq('id', a[key]), b ) , b))

Pretty good solution. Just keeping here for future use :)

const sortBy = (array, values, key = 'id') => ((map) => values.reduce((a,i) => 
a.push(map[i]) && a,[]))(array.reduce((a,i) => (a[i[key]] = i) && a, {}));

Usage

a = [2,3,1,4]
b = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]

sortBy(b, a) // [{id: 2}, {id: 3}, {id: 1}, {id:` 4}]

You could use methods of the Array object:

 var a = [2, 3, 1, 4]; var b = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]; var c = []; a.forEach(el => c.push(b.find(e => e.id == el))); console.log(JSON.stringify(c, 0, 2));

Here's an alternative construction using Ramda that might be a bit more succinct, and the functions are pretty easy to repurpose for other things.

 const {indexBy, prop, map, flip} = R const a = [2,3,1,4] const b = [{id: 1}, {id: 2}, {id: 3}, {id: 4}] const toIndexById = indexBy(prop('id')) const findIndexIn = flip(prop) const c = map(findIndexIn(toIndexById(b)), a) console.log(c)
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

I am going to iterate on arcseldon 's solution.

The right approach here is to flip the problem, instead of "sorting a based on the order given by b " , I'd rather "enrich b with data coming from a "

This lets you maintain the time complexity of this function down to O(n) , instead of bringing it up to O(n2) .

 const pickById = R.pipe( R.indexBy(R.prop('id')), R.flip(R.prop), ); const enrich = R.useWith(R.map, [pickById, R.identity]); // ===== const data = [2, 3, 1, 4]; const source = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; console.log( enrich(source, data), );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>

Simply You could use Reduce

 var a = [2, 3, 1, 4]; var c = a.reduce((res,item)=>([...res,{'id':item}]),[]) console.log(c)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM