【问题标题】:Javascript and collectionsJavascript 和集合
【发布时间】:2026-01-25 23:30:01
【问题描述】:

想象你在 javascript/NodeJs 中有 3 个列表/数组

  1. 数组包含 1.000.000 个数据项
  2. 数组包含 100.000 个数据项
  3. 数组包含 50.000 个数据项

每个数据项都是一个具有 2 个属性的对象 - 例如日期和价格。 数组 2 和 3 中的所有项都是数组 1 中项的子集/子列表。

我的问题:我如何以最快的方式 - 匹配数组 1 中每个项目的所有日期 - 与数组 2 和 3 中的所有日期 - 匹配数组 1 中的每个项目?

我习惯了 .NET/C# - 'contains(item)' 之类的东西很好......现在在 NodeJS 中我使用 3 个 for 循环 - 这是一种减慢速度的方式......我需要一些索引之类的以加快进程...

数据示例如下:

输入:

array 1: 1,2,3,4,5,6,7,8,10
array 2: 2,3,5,7,9
array 3: 1,4,5,10

输出(写入文件):

1,'',1
2,2,''
3,3,''
4,'',4
..ect...

【问题讨论】:

  • 是否有重复的日期?日期是 Date 对象还是字符串?您能否包含一个示例对象,其中包含将匹配和不匹配的日期和价格?另外,您使用了多少 RAM?
  • 在我的 postgresql 数据库中,日期保存为“带时区的时间戳”。 “2014-12-01 05:33:56.761199+00”数据项示例:new TradePoint(date, price) - 如果日期匹配,则将价格添加到新列表/行...关于内存,只要可能... 2-4-8gb...每个列表中没有重复的日期/项目。
  • @PabloDK,数组是否保证提前排序?
  • 你使用数组而不是对象有什么原因吗?
  • 让我重新表述我的问题:不要使用具有 2 个属性的小对象并将其推送到数组中。是否有任何其他结构/类型的集合在 javascript 中更快/性能更好(尤其是关于尽快在数组中找到给定对象)?

标签: javascript arrays node.js performance indexing


【解决方案1】:

如果数组是排序的我猜JS中最快的数组交集算法是这样的

function intersect(a1, a2)
{
  var a1i = 0,
      a2i = 0,
    isect = [];

  while( a1i < a1.length && a2i < a2.length ){
    if (a1[a1i].date < a2[a2i].date) a1i++;
     else if (a1[a1i].date > a2[a2i].date) a2i++;
     else {isect.push(a1i); // they match
           a1i++;
           a2i++;}
  }

  return isect;
}

一旦你得到了交集索引,你就可以轻松地构建你想要的输出。

但是,如果您想提出一个很酷的工具...那么为什么不发明Array.prototype.intersect()

Array.prototype.intersect = function(...a) {
  return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
     arr = [0,1,2,3,4,5,6,7,8,9];

document.write("<pre>" + JSON.stringify(arr.intersect(...arrs)) + "</pre>");

【讨论】:

    【解决方案2】:

    这是javascript!考虑将数组重组为对象,以便 Date 属性成为键,例如: var arr2 = { '2016-05-13 00:00:01': { prop: 'value', prop2: 'value' }, '2016-05-13 00:00:02': { prop: 'value' , prop2: '值' }, ... }; 这样 arr2[date] 要么返回一个对象,要么返回未定义。如果你有一个对象,将其转换为适合输出的字符串;否则写''或其他。

    【讨论】:

    • 非常感谢!我之前没有意识到值/道具和键在 javascript 中是如何工作的——但现在我知道了!我刚刚测试了...一个基本的 for 循环,有 1000 万次迭代(插入到对象中) - 需要 2650 毫秒,而对象中的键查找需要 1000 万个对象,只需 3 毫秒!!
    【解决方案3】:
    var t = {}
    // loop through once and create a constant time lookup
    arr1.forEach(function(item){t[item.date] = {1: item.price, 2: null, 3:null})
    arr2.forEach(function(item){if (t[item.date]) t[item.date].2 = item.price})
    arr3.forEach(function(item){if (t[item.date]) t[item.date].3 = item.price})
    

    这将是一个线性操作,首先对数据进行排序的权衡可能值得也可能不值得花时间进行排序。

    无论哪种方式,它都与三重 JOIN 大致相同,我提供的解决方案是 O(N),其中嵌套循环可能是 O(N^3),排序的解决方案仍然可能是 O(Nlog(N )) 只是猜测。

    如果日期已经排序,您可能会将日期分桶或进行某种基数搜索,这可能会加快速度。

    见:https://en.m.wikipedia.org/wiki/Radix_tree

    你也可以通过 Promise 来实现,让它异步运行:

    var t = {}
    // loop through once and create a constant time lookup
    arr1.forEach(function(item){t[item.date] = {1: item.price, 2: null, 3:null})
    
    var promiseArray = arr2.map(function(item){
        return Promise.resolve(item)
            .then(function(item){
                  if (t[item.date]) t[item.date].2 = item.price})
             })
    // concat the two promise arrays together 
    promiseArray.concat(arr3.map(function(item){
        return Promise.resolve(item)
            .then(function(item){
                  if (t[item.date]) t[item.date].3 = item.price})
             }))
    // resolve all the promises
    Promise.all(promiseArray)
        .then(function(){
            // t has results
            debugger
        })
    

    【讨论】:

    • 非常感谢!好吧,正如您正确猜到的那样,我不是 javascript 专家(还)-我习惯于诸如 C# 等类型的语言...我理解您示例中的所有内容都很难...但我试图绕开我的脑袋-单个踩踏的过程如何可以通过诸如异步之类的东西更好地执行-更快/更慢...当它仍然只使用1个CPU内核时?是不是因为它等待的工作是由完全其他的线程/进程/CPU核心(默认?)完成的,否则我不明白“异步”之类的东西如何以及为什么可以改进任何事情?您如何同时在 4 个 CPU 内核上运行它?
    • @PabloDK 有趣的问题。与节点的并发是通过cluster 模块完成的,它需要消息传递。它的好处是在处理大量传入请求时,或多或少地请求在每个核心之间得到负载平衡。通过用 Promise 编写它,它使单个线程/核心/事件循环有机会在不阻塞的情况下处理其他传入请求。因此,如果在 promise 等待解决时有其他请求进来,好处就会出现。
    • 对于共享内存的答案,请参阅:*.com/questions/10965201/… 我认为那里的 cmets 建议 ems 但将var t 卸载到 redis 实例并在那里访问它可能会更容易分叉的承诺。
    • 只是另一个想法,它既是节点的优点也是缺点。
    • 我不记得所有的细节了...但是有一次我看到一个来自 StroopLoop 的人的视频...谈论这个问题...我记得...集群不是“真正的”多 CPU 核心编程......我的问题是根本不处理许多请求 - 它的所有服务器端代码/服务器应用程序......所以它只是原始服务器能力和优化代码的问题 - 的我怎么看...
    【解决方案4】:

    我会尝试首先按您的键属性 (Date, afaiu) 对所有数组进行排序(如果尚未排序),然后在第一个数组上使用单个 for 循环,光标位于另外两个数组中,这将移动到仅当当前项目已写入输出时,下一个项目。这样就不会在整个数组中进行任何“包含”搜索。

    例子:

    var j = 0;
    var k = 0;
    for( var i = 0; i < array1.length; ++i ) {
        var out1 = array1[i].date;
        var out2 = j < array2.length && array2[j].date == out1 ? array2[j++].value : '';
        var out3 = k < array3.length && array3[k].date == out1 ? array3[k++].value : '';
        output( out1, out2, out3 );
    }
    

    【讨论】:

    • 这正是我目前正在做的......但我虽然在 javascript 中必须有更快的方法?是的 - 所有列表/数组都按日期按相同顺序排序...
    • @PabloDK,请查看我刚刚添加的示例。你确定你在做这样的事情吗?因为你写了你有 3 个 for 循环,而可能只使用一个。
    • 您必须将长度存储在变量中。
    • @Knu,天哪,这只是一个带有 2 个额外光标的单个 for 循环的概念,我在手机上输入了它。当然,在循环内重复解析为常数值的任何内容,都必须在该循环之外仅计算一次。但是这个优化对于问题的作者来说是如此明显,所以我决定不要让这个例子过于复杂。
    • @JustAndrei - 我看到你的代码循环并遍历 array1 ......但我看到代码的方式 - 它不会循环遍历 array2 或 array3?它只是进行一次比较 (var out2 = j