【问题标题】:Improve performance of forEach iteraction提高 forEach 迭代的性能
【发布时间】:2022-01-02 22:10:59
【问题描述】:

我正在尝试针对较小的 (2K) 数组过滤较大的数组 (10K) 记录。

使用此代码大约需要 5 秒才能完成。

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

我使用 forEach 而不是 filter(在 90 毫秒时要快得多)的原因是我在同一个 (10K) 数组中寻找两个不同的项目。即使我使用了过滤器,我仍然需要将主数组和辅助数组中的数据附加到包含它们所有数据的最终对象。

我最好的选择是什么来优化它,让它不会那么慢,但满足我对包含项目详细信息和订单详细信息的最终数组的要求?

【问题讨论】:

  • 假设id 是唯一的,您当然可以添加break
  • 您应该将您的localOrdersonlineOrders 转换为Maps 或objects,使用ID 作为键,这样查找平均变成O(1)。这将使您的代码只执行k*10000 操作而不是k*5000*10000
  • 如果id是唯一的,使用break,或者你应该使用Map而不是数组,如果allItemslocalOrders, onlineOrders是排序的,那么使用二分查找,
  • @JavaScript 我将它转换为 for of 循环,因为 foreach 不支持 break 并且从 4300 毫秒到大约 4100 毫秒。这是一个进步,但仍然很慢。

标签: javascript performance loops foreach


【解决方案1】:

假设您的三个列表具有这种结构:

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 }];

第一个想法:通过localOrdersonlineOrders 比通过allItems 便宜得多,这可能是一个更大的列表。此外,在您当前的代码中,您正在迭代最长列表中的每个对象,并且对于这些对象中的每一个,您正在迭代整个localOrdersonlineOrders。因此,最好反其道而行之,从较短的列表开始。

对于每个本地订单,您想在allItems.find(item => item.id===localOrder.itemId)的大列表中找到对应的项目。与.forEach() 不同,.find() 在找到元素后立即停止遍历数组,这样可以为您节省很多周期。

所以你可以从这样的开始:

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

这是本地订单,现在是在线订单:

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

它可以工作,但它有点笨拙,因为你重复了几乎相同的代码两次。如果你有第三种类型的订单,你会重复同样的代码三四次等等。所以,一个更聪明的做事方式是将订单放在一个对象中,这样你就可以拥有尽可能多的类型随心所欲地订购,无需重复,如下所示:

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);

【讨论】:

  • 我已经尝试过了,几乎是完成时间的两倍。
  • 我在codepen 中做了一个基准测试。 allItems 中有 10k 个元素,你的方法需要 6.54 毫秒,我的需要 2.07 毫秒(快 3 倍)。拥有 100k 个项目,你的 16 毫秒,我的 6 毫秒(快 2.5 倍)。拥有 100 万个项目,你的 71 毫秒,我的 5 毫秒(快 14 倍)。拥有 1000 万个项目,你的 650 毫秒,我的 5.8 毫秒(快 112 倍)。自己检查。我的方法只是不能比你的慢,因为你的方法总是迭代每个数组的每一项,它是最长和最慢的。你的基准是什么?我可以检查一下吗?
  • 我在计算前后使用了javascript时间,但我也确信它需要5+秒,因为它很慢。我会尝试做一个 codepen,填充 allItems 的数据是 6MB。
猜你喜欢
  • 1970-01-01
  • 2021-07-04
  • 1970-01-01
  • 2021-06-11
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
相关资源
最近更新 更多