【问题标题】:How to perform array join in Node.js efficiently/fast similar to MongoDB $lookup?如何在 Node.js 中高效/快速地执行数组连接,类似于 MongoDB $lookup?
【发布时间】:2019-12-20 21:39:49
【问题描述】:

我想在 Node.js 中执行 $lookup,类似于来自 MongoDB 的 $lookup 聚合。

我有一个解决方案,但我不确定在两个数组中的每个数组中使用更多对象或使用更大对象时它的执行速度有多快。

let users = [
    {userId: 1, name: 'Mike'}, 
    {userId: 2, name: 'John'}
    ]
let comments = [
    {userId: 1, text: 'Hello'}, 
    {userId: 1, text: 'Hi'}, 
    {userId: 2, text: 'Hello'}
    ]

let commentsUsers = [
    {userId: 1, text: 'Hello', user: {userId: 1, name: 'Mike'}}, 
    {userId: 1, text: 'Hi', user: {userId: 1, name: 'Mike'}}, 
    {userId: 2, text: 'Hello', user: {userId: 2, name: 'John'}}
    ] //Desired result

我知道这可以通过 ECMA6 阵列轻松完成。例如:

let commentsUsers = comments.map(comment => {comment, users.find(user => user.userId === comment.userId)} )

对于大量用户来说,这是一种有效的方法,例如。 100万用户。 lodash 与这个或任何其他更专业的库相比如何?有没有更好的方法来使用香草 JS 来做到这一点,例如。使用 Array.prototype.reduce()?可以以任何方式使用索引来提高连接的性能吗?

编辑:

我的理想解决方案

let users    = [{userId:1,name:'Mike'},{userId:2,name:'John'}]
let comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];

let usersMap = new Map(users.map(user => [user.userId, user]))
let commentsUsers = comments.map(comment => ({...comment, user: usersMap.get(comment.userId)}))

console.log(commentsUsers)

感谢您的反馈!

【问题讨论】:

  • 期望的结果不是一个有效的对象。您需要为嵌套对象分配一个键,类似于user。

标签: javascript arrays node.js mongodb ecmascript-6


【解决方案1】:

您想要的结果不是正确的数据结构。您缺少对象的密钥,例如{userId: 1, name: 'Mike'}。我添加了user 作为索引解决方案的键值。

首先我创建一个Map,其中userId 将是我们的循环值。之后,我只是用map 迭代comments,将每个对象转换为一个包含所有comment 信息以及一个新的k-v 用户对的新对象。对于那对我们不再需要使用find,而是我们有一个简单的 HashMap get 调用。

时间复杂度方面,这会将代码从 O(n^2) 更改为 O(n)。

let users    = [{userId:1,name:'Mike'},{userId:2,name:'John'}], 
    comments = [{userId:1,text:'Hello'},{userId:1,text:'Hi'},{userId:2,text:'Hello'}];

function mergeCommentUser(users, comments) {
  let map = new Map(users.map(v => [v.userId, v]));
  return comments.map(o => ({...o, user: map.get(o.userId)}));
}

console.log(JSON.stringify(mergeCommentUser(users,comments)))

根据您的需要(并节省冗余),您还可以更改以下行:

let map = new Map(users.map(v => [v.userId, v]));

改为:

let map = new Map(users.map(v => [v.userId, v.name]));

您的结果将如下所示:

[
    {"userId":1,"text":"Hello","user":"Mike"},
    {"userId":1,"text":"Hi","user":"Mike"},
    {"userId":2,"text":"Hello","user":"Paul"}
]

否则,您可以省略 comment.userId,而是将完整用户添加到对象中以另一种方式避免冗余。

【讨论】:

  • 做得很好,我没想到要在地图中包含整个用户对象,只是一个索引引用:P
  • @Kobe,谢谢!可能有一种更清洁的方法,但我最近才开始使用 Map。
  • 是的,这样的索引真的很有用:)
【解决方案2】:

目前,您提供的代码示例为 O(n * m),或者,O(n2)。您可以在 users 数组中创建每个 userId 及其各自索引的映射,然后您可以通过索引直接访问它,而不是查找用户。这样会把时间减少到O(n + m),也就是O(n)。

代码如下所示:

const users = [{ userId: 1, name: "Mike" }, { userId: 2, name: "John" }];
const comments = [
  { userId: 1, text: "Hello" },
  { userId: 1, text: "Hi" },
  { userId: 2, text: "Hello" }
];

const map = new Map(users.map((o, i) => [o.userId, i]));

console.log(
  comments.map(o => {
    const index = map.get(o.userId);
    return index !== undefined
      ? {
          comment: o.text,
          user: users[index]
        }
      : o;
  })
);

显然,您可以修改最终结果,但这种方法会比您建议的方法更有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-28
    • 2015-12-12
    • 1970-01-01
    • 2012-12-05
    • 2019-02-06
    • 2013-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多