JavaScript和集合

3

假设您在Javascript/NodeJs中有3个列表/数组:

  1. 包含1,000,000个数据项的数组
  2. 包含100,000个数据项的数组
  3. 包含50,000个数据项的数组

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

我的问题是:如何以最快的方式将数组1中每个单独的数据项的所有日期与数组2和3中的所有日期进行匹配?

我习惯使用.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? - jmunsch
在我的PostgreSQL数据库中,日期保存为“带时区的时间戳”。 “2014-12-01 05:33:56.761199+00”数据项示例:new TradePoint(date, price) - 如果日期匹配,则将价格添加到新列表/行中...关于内存,尽可能少...2-4-8GB...每个列表中没有重复的日期/项目。 - PabloDK
1
@PabloDK,这些数组事先保证已经排序了吗? - zzzzBov
你使用数组而不是对象,有什么原因吗? - Soren
让我重新表达我的问题:除了使用具有2个属性的小对象并将其推入数组之外,是否还有其他更快/性能更好的JavaScript集合结构/类型(特别是关于尽快查找给定对象在数组中的位置)? - PabloDK
4个回答

2
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

您还可以使用promises来异步运行它:

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核心(默认情况下)完成吗?否则,我不明白为什么像“async”这样的东西可以改善任何事情?您如何同时在4个CPU核心上运行此代码? - PabloDK
@PabloDK 有趣的问题。在Node中,使用cluster模块实现并发需要进行消息传递。当处理大量传入的请求时,它的好处在于请求在每个核心间得到负载均衡。通过使用Promises编写代码,可以使单线程/核心/事件循环能够处理其他传入的请求而不会被阻塞。因此,如果在等待解析Promise时有其他请求进来,则可以获得好处。 - jmunsch
关于共享内存的答案,请参见:https://dev59.com/AWgu5IYBdhLWcg3w8Lev。我认为那里的评论建议使用 ems,但是将 var t 转移到 Redis 实例中并在分叉的 Promise 中访问可能更容易些。 - jmunsch
又想到一个问题,这对于 Node 来说既是好处也是坏处。 - jmunsch
我不记得所有的细节,但我曾经看过一段视频,其中一个来自StroopLoop的人谈到了这个问题。据我回忆,集群并不是真正的“多CPU核心编程”...我的问题不在于处理许多请求 - 这都是服务器端代码/服务器应用程序...所以这只是原始服务器功率和优化代码的问题 - 在我看来... - PabloDK
@PabloDK,如果“真正”的并发是问题,那么Node.js就不是正确的工具。 - jmunsch

1
这是JavaScript!考虑将数组重构为对象,使日期属性成为键,例如:var arr2 = {'2016-05-13 00:00:01': {prop:'value',prop2:'value'},'2016-05-13 00:00:02': {prop:'value',prop2:'value'},...}; 这样,arr2 [date] 将返回一个对象或未定义。如果得到一个对象,请将其转换为适合输出的字符串;否则写入''或其他内容。

非常感谢!以前我并不了解JavaScript中的值/属性和键是如何工作的,但现在我明白了!我刚刚测试了一个基本的for循环,进行了1000万次迭代(插入到对象中),花费了2650毫秒,而在拥有1000万个对象的对象中进行键查找只需3毫秒! - PabloDK

1
我会尝试首先按照您的键属性(Date,如果尚未排序)对所有数组进行排序,然后在第一个数组上使用单个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
@Knu,天哪,这只是一个包含2个附加游标的单个for循环概念,而我是在手机上输入的。当然,在循环内重复解析为常量值的任何内容都必须在该循环之外计算一次。但对于提问者来说,这种优化是如此明显,以至于我决定不要让示例过于复杂化。 - JustAndrei
@JustAndrei - 我看到你的代码循环遍历array1...但是我认为代码没有循环遍历array2或array3?它只进行了一次比较(var out2 = j < array2.length && array2[j].date == out1 ? array2[j++].value : '';)?如果我错了或者漏掉了什么,请纠正我。 - PabloDK
@JustAndrei:我确实知道符号“X?Y:Z”...但这并不改变 - 你拥有的唯一循环只是“for(var i=0;i<array1.length;++i)”吗? 其余部分只是一个简单的IF语句... - PabloDK
@PabloDK,i是array1的索引,而j是array2的索引。 i由for操作符递增,而j则通过array2[j++]递增,即仅当找到匹配日期并发送到输出时。 - JustAndrei
显示剩余3条评论

0
如果数组已排序,我想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>");


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接