45

I have an array with keys like so:

['asdf12','39342aa','12399','129asg',...] 

and a collection which has these keys in each object like so:

[{guid: '39342aa', name: 'John'},{guid: '129asg', name: 'Mary'}, ... ]

Is there a fast way to sort the collection based on the order of keys in the first array?

silintzir
  • 762
  • 1
  • 6
  • 13

5 Answers5

89
var sortedCollection = _.sortBy(collection, function(item){
  return firstArray.indexOf(item.guid)
});
idbehold
  • 16,833
  • 5
  • 47
  • 74
15

Here is just a simple add to the accepted answer in case you want to put the unmatched elements at the end of the sortedCollection and not at the beginning:

const last = collection.length;

var sortedCollection = _.sortBy(collection, function(item) {
  return firstArray.indexOf(item.guid) !== -1? firstArray.indexOf(item.guid) : last;
});
Alvaro
  • 1,853
  • 18
  • 24
  • 3
    Even though this wasn't the accepted answer, to me it is, as it seems more natural to put the unknown elements at the end of the array, after all the sorted items, instead of the beginning – pizzae Apr 24 '22 at 13:37
11

Input:

var data1 = ['129asg', '39342aa'];
var data2 = [{
    guid: '39342aa',
    name: 'John'
}, {
    guid: '129asg',
    name: 'Mary'
}];
  1. First create an index object, with _.reduce, like this

    var indexObject = _.reduce(data2, function(result, currentObject) {
        result[currentObject.guid] = currentObject;
        return result;
    }, {});
    
  2. And then map the items of the first array with the objects from the indexObject, like this

    console.log(_.map(data1, function(currentGUID) {
        return indexObject[currentGUID]
    }));
    

Output

[ { guid: '129asg', name: 'Mary' },
  { guid: '39342aa', name: 'John' } ]

Note: This method will be very efficient if you want to sort so many objects, because it will reduce the linear look-up in the second array which would make the entire logic run in O(M * N) time complexity.

thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • I really like this answer, however it doesn't cover cases when there are multiple elements with the same guid. Just pointing it out. – Ando Jul 03 '23 at 20:40
1

This is the efficient & clean way:

(Import lodash identity and sortBy):

TS:

function sortByArray<T, U>({ source, by, sourceTransformer = identity }: { source: T[]; by: U[]; sourceTransformer?: (item: T) => U }) {
  const indexesByElements = new Map(by.map((item, idx) => [item, idx]));
  const orderedResult = sortBy(source, (p) => indexesByElements.get(sourceTransformer(p)));
  return orderedResult;
}

Or in JS:

function sortByArray({ source, by, sourceTransformer = _.identity }) {
    const indexesByElements = new Map(by.map((item, idx) => [item, idx]));
    const orderedResult = _.sortBy(source, (p) => indexesByElements.get(sourceTransformer(p)));
    return orderedResult;
}
Shl
  • 3,130
  • 1
  • 17
  • 16
0

You can use indexBy(), and at() to sort your collection. The advantage being that concise code and performance. Using sortBy() here does the trick, but your external array is already sorted:

var ids = [ 'cbdbac14', 'cf3526e2', '189af064' ];

var collection = [
    { guid: '189af064', name: 'John' },
    { guid: 'cf3526e2', name: 'Julie' },
    { guid: 'cbdbac14', name: 'James' }
];

_(collection)
    .indexBy('guid')
    .at(ids)
    .pluck('name')
    .value();
// → [ 'James', 'Julie', 'John' ]

Using at(), you can iterate over the sorted external collection, building a new collection from the source collection. The source collection has been transformed into an object using indexBy(). You do this so at() has key-based access for each of it's ids.

Adam Boduch
  • 11,023
  • 3
  • 30
  • 38