简体   繁体   中英

Most efficient way to sort a collection of arrays based off the order of a seperate array

Yes I am aware of similar questions but it doesn't quite cover my use case here.

I'm having trouble finding a way to bring the time complexity of this down

I have two objects like so

const people = [
  {
    name: 'Steve',
    id: 1,
    fruitInBasket: 6
  },
  {
    name: 'James',
    id: 2,
    fruitInBasket: 4
  }
]
const homes = [
  {
    id: 1,
    familyMembers: [
      {
        name: 'James',
        id: 2
      },
      {
        name: 'Steve',
        id: 1
      }
    ]
  },
  {
    id: 2,
    familyMembers: [
      {
        name: 'James',
        id: 2
      },
      {
        name: 'Steve',
        id: 1
      }
    ]
  }
]

so one is a collection of people with a count of fruit in a basket and the other is a collection of homes and within each home is the same users as in the people collection.

Now I want to order the users in each home based on the count of fruitInBasket so I have done this

// create an empty table to store the order of the people
let orderTable = {};

// order the people based off the count in fruitInBasket using lodash orderBy
people = orderBy(people, ['fruitInBasket'], ['desc']);

// create the table 
orderTable = people.reduce((acc, item, index) => {
  return {
    ...acc,
    [item.id]: index
  }
}, {});

// order the people in each home based on the order in the `orderTable`
homes.forEach((home) => {
  let members = [];
  home.familyMembers.forEach((member) => {
    let i = orderTable[member.id];
    members[i] = member;
  });
  home.familyMembers = members;
})

so immediately you can see a nested for loop - which is never ideal.. but I can't figure out a way around this. This method has to sort through quite a lot of data and I am noticing huge performance issues.

Any help would be appreciated!

This should be O(N log N). Its performance bottleneck is the one time sort. Everything else is just O(N) iteration. Some microoptimizations still possible.
Generate an ordered map lookup table.
Just move arrays based on that mapping.

There is a hpc sort library that is measured around 10-40x faster than built-in JavaScript on benchmarks that can be added to increase performance.

I'm not sure, but is the familyMember table the same for every home object? Can you not just copy the same familyMember array to every object or do they have different properties?
Extra optimization per home could be to convert above table to index to index mapping, so that native level array indexing will be used for subsequent ordering.

 const orderMap = Object.fromEntries(people.sort((x,y)=>x.fruitInBasket-y.fruitInBasket).map(({id},i)=>[id,i])) // O(N)+O(NlogN) homes.forEach(home=>{ const {familyMembers:fms} = home const arr = new Array(fms.length) //may want to prefill to maintain PACKED array: https://v8.dev/blog/elements-kinds#avoid-creating-holes for(const fm of fms) arr[ orderMap[fm.id] ] = fm home.familyMembers = arr }) // map lookup O(N) console.log(homes)
 <script> const people = [ { name: 'Steve', id: 1, fruitInBasket: 6 }, { name: 'James', id: 2, fruitInBasket: 9 } ] const homes = [ { id: 1, familyMembers: [ { name: 'James', id: 2 }, { name: 'Steve', id: 1 } ] }, { id: 2, familyMembers: [ { name: 'James', id: 2 }, { name: 'Steve', id: 1 } ] } ] </script>

You can filter and sort:

 const people = [ { name: 'Steve', id: 1, fruitInBasket: 6 }, { name: 'James', id: 2, fruitInBasket: 4 }, { name: 'Cesar', id: 3, fruitInBasket: 14 } ] const homes = [ { id: 1, familyMembers: [ { name: 'James', id: 2 }, { name: 'Cesar', id: 3 }, { name: 'Steve', id: 1 } ] }, { id: 2, familyMembers: [ { name: 'James', id: 2 }, { name: 'Steve', id: 1 } ] } ] homes.forEach(function(home){ home.familyMembers.sort((a,b)=>people.find(x=>x.id == a.id).fruitInBasket - people.find(x=>x.id == b.id).fruitInBasket) }) console.log(homes)

Explanations:

You will iterate through homes:

homes.forEach(function(home){

You will sort the members:

.familyMembers.sort((a,b)

To sort, you have to get the fruits of the members, so you find the correct ID then take the correct property:

people.find(x=>x.id == a.id).fruitInBasket

Then you compare:

(a,b)=>people.find(x=>x.id == a.id).fruitInBasket - people.find(x=>x.id == b.id).fruitInBasket

If what you're looking for is performance, you should change people strucutre:

const people = {
  1:  {
    name: 'Steve',
    fruitInBasket: 6
  },
  2: {
    name: 'James',
    fruitInBasket: 4
  }
}

This way you can retrieve fruits more easily:

people[id].fruits

Also, if your "id" is defined somewhere, don't define it in another place. Your homes should look like this:

const homes = {
  1:  {
    familyMembers: [1, 2, 3]  
  },
  2: {
    familyMembers: [1,2]
  }
}

So your algorithm goes like this:

 const people = { 1: { name: 'Steve', fruitInBasket: 6 }, 3: { name: 'James', fruitInBasket: 4 }, 2: { name: 'Cesar', fruitInBasket: 9114 } } const homes = { 1: { familyMembers: [1, 2, 3] }, 2: { familyMembers: [1,2] } } Object.keys(homes).forEach(function(k){ homes[k].familyMembers.sort((a,b)=>people[a].fruitInBasket - people[b].fruitInBasket) }) console.log(homes)

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