【问题标题】:Performant way to compare two array of objects with v8 & filter/find将两个对象数组与 v8 和过滤器/查找进行比较的高性能方法
【发布时间】:2019-09-21 15:11:52
【问题描述】:

我有 2(嗯,> 2) 个对象数组,其结构如下:

const mainArr = [
  { article: "somename-1", price: 10, found: [] },
  { article: "somename-2", price: 20, found: [] },...
];
const toCompareArr = [
  { article: "somename-1", articleFormatted: "0somename-1", price: 20 },
  { article: "somename-3", articleFormatted: "0somename-3", price: 20 },...
];

对于 ma​​inArr,我尝试在第二个 toCompareArr 数组中查找所有出现的情况(基本上 - 我需要为不同的供应商创建价格比较器),如下所示:

let i = 0;
let j = 0;
const iMax = mainArr.length;
const jMax = toCompareArr.length;
for (; i < iMax; i++) {
  for (; j < jMax; j++) {
    if (
      mainArr[i].article === toCompareArr[j].article ||
      mainArr[i].article === `00-${toCompareArr[j].articleFormatted}`
    ) {
      mainArr[i].found.push(toCompareArr[j]);
    }
  }

  j = 0;
}

// also tried like this (for + for seems to be more speedy):
/*
let found;
for (; i < iMax; i++) {
  found = toCompareArr.filter(
    item =>
      item.article === mainArr[i].article ||
      item.articleFormatted === mainArr[i].article
  );

  if (found) {
    mainArr[i].found.push(found);
  }
}
*/

作为结果我想得到:

[
  {
    article: "somename-1",
    price: 10,
    found: [
      { article: "somename-1", articleFormatted: "0somename-1", price: 20 }
    ]
  },
  { article: "somename-2", price: 20, found: [] }
];

但是当我有大量数据时它会非常慢(通常每个数组项大约有 80k-100k 个数组项)

我卡住了......也许有一种方法可以提高过滤器的性能并在这里找到?我正在使用最新的 Node 和 V8 功能。现在比较需要大约 5 分钟,如果它是 50%(假设是 2 分钟 - 更好)

也许我应该以某种方式彻底改造我的逻辑?使用一些算法(想知道 - 哪一个)

【问题讨论】:

  • 请同时添加想要的结果。
  • 顺便说一句,mainArr[i].found 从未定义过。
  • "80k-100k 数组项" - 呃,您是在尝试使用 node.js 实现内存数据库吗?
  • 基本方法是创建某种 index,以便您可以按名称执行快速查找,而不是对另一个类似大小的数组进行 100,000 次详尽搜索。
  • @Bergi 不,不是真的 :) 数据是从电子表格中解析出来的……这里有什么建议吗?喜欢使用 mongo/sqlite 等?

标签: javascript node.js algorithm v8


【解决方案1】:

当我有大量数据时,速度非常慢 - 通常每个数组项大约有 80k-100k 个

为此任务使用database。一个好的 DBMS 应该能够使用hash join 或类似技术在不到一秒的时间内完成此比较。当然,如果您坚持,您可以在 node.js 中重新实现 - 使用文章标识符作为 Map 中的键。

【讨论】:

    【解决方案2】:

    您可以使用Map。

    const
        mainArr = [{ article: "somename-1", price: 10, found: [] }, { article: "somename-2", price: 20, found: [] }],
        toCompareArr = [{ article: "somename-1", articleFormatted: "0somename-1", price: 20 }, { article: "somename-3", articleFormatted: "0somename-3", price: 20 }],
        map = mainArr.reduce((m, o) => m.set(o.article, o), new Map);
    
    for (let item of toCompareArr) {
        let object = map.get(item.article) || map.get('00-' + item.articleFormatted);
        if (!object) continue;
        object.found.push(item);
    }
    
    console.log(mainArr);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 添加一些背景解释为什么这会有所帮助:原始方法的问题是两个嵌套的 for 循环具有“二次”总运行时间:对于长度为 n 的数组,您将有n 外部迭代次数n 内部迭代。当您的数组很大时,n^2 会快速增长。 Map 技术避免了这种情况:在构建 Map 之后(这是一个循环,在这种情况下隐藏在 mainArr.reduce 内),您只需要迭代 toCompareArr 一次。
    • 顺便说一句,是否有可能以某种方式调整上述代码以与 LIKE 进行比较?例如:mainArr 有带有文章“somePartNR”的项目,toCompareArr 有“somePartNRwithSomeAdditional”...在 sql 中我可以只使用文章 %,但是如何使用地图?
    • 这取决于应该在哪里进行比较。你有一个实际的例子吗?
    • 点赞:const mainArr = [ { article: "0500", price: 10, found: [] }, { article: "0600", price: 20, found: [] },... ]; const toCompareArr = [ { article: "500A", articleFormatted: "0500A", price: 20 }, { article: "600A", articleFormatted: "0500A", price: 20 },... ];
    • 所以应该是类似let object = map.get(item.article) || map.get(item.articleFormatted) || map.get.startsWith(item.article)); slicing end of the article - 不是解决方案:)
    猜你喜欢
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 2016-10-24
    • 1970-01-01
    相关资源
    最近更新 更多