Michael
Michael

Reputation: 545

Improve performance of forEach iteraction

I am attempting to filter a larger array (10K) records against a smaller (2K) array.

Using this code takes almost 5 seconds to complete.

let inventory = []
allItems.forEach((item) => {
  localOrders.forEach((order) => {
    if (item.id === Number(order.itemId)) {
      inventory.push({
        item,
        order,
        type: 'local',
      })
    }
  })
  onlineOrders.forEach((order) => {
    if (item.id === Number(order.itemId)) {
      inventory.push({
        item,
        order,
        type: 'online',
      })
    }
  })
})

return inventory

The reason I use forEach rather than filter (which is much faster at 90 ms) is I am looking for two different items in the same (10K) array. Even if I used filter, I would still need to attach the data from primary and secondary array to a final object that includes all the data for them.

What is my best option to optimize this so it isn't so slow, but satisfies my requirement of a final array that includes item details and order details?

Upvotes: 0

Views: 541

Answers (1)

Jeremy Thille
Jeremy Thille

Reputation: 26400

Assuming your three lists have this structure :

const allItems = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 9 }];
const localOrders = [{ itemId: 3 }, { itemId: 7 }];
const onlineOrders = [{ itemId: 5 }, { itemId: 7 }];

First idea: it is much cheaper to go through localOrders and onlineOrders than through allItems, which can be a MUCH larger list. Besides, in your current code, you are iterating EACH object in the longest list, and for each one of these objects, you are iterating the entirety of localOrders and onlineOrders. So it's a good idea to do the opposite and start with the shorter lists.

For each local order, you want to find the corresponding item in the big list with allItems.find(item => item.id===localOrder.itemId). Unlike .forEach(), .find() stops traversing the array as soon as it finds the element, which saves you a lot of cycles.

So you can start with something like this :

let inventory = localOrders.map(localOrder => ({
  item : allItems.find(item => item.id===localOrder.itemId),
  order : localOrder,
  type: 'local',
}))

That's for the local orders, now the online orders :

inventory = inventory.concat(onlineOrders.map(onlineOrder => ({
  item : allItems.find(item => item.id===onlineOrder.itemId),
  order : onlineOrder,
  type: 'online',
})))

It works, but it's a little clumsy though, because you are repeating pretty much the same code twice. And if you have a third type of order, you will repeat the same code three times, four times, etc. So, a more clever way of doing things is to put the orders in an object, so you can have as many types of orders as you want without repeating yourself, something like this :

const allItems = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 9 }];

const orders = {
  "local" : [{ itemId: 3 }, { itemId: 7 }],
  "online" : [{ itemId: 5 }, { itemId: 7 }],
  "delayed" : [{ itemId: 8 }]
}

let inventory = [];

Object.keys(orders).forEach( type => { // type=="local", type=="online"
  inventory = inventory.concat(orders[type].map(localOrder => ({
    item : allItems.find(item => item.id===localOrder.itemId),
    order : localOrder,
    type,
  })))
})

console.log(inventory);

Upvotes: 3

Related Questions