gpt4 book ai didi

javascript - 对数组交叉点进行计数和排序

转载 作者:太空宇宙 更新时间:2023-11-04 01:50:29 25 4
gpt4 key购买 nike

我有这个架构

module.exports = function(conn, mongoose) {
// var autoIncrement = require('mongoose-auto-increment');


var UsersSchema = new mongoose.Schema({

first_name: String,
last_name:String,
sex: String,
fk_hobbies: []


}
, {
timestamps: true
}, {collection: 'wt_users'});




return conn.model('wt_users', UsersSchema);
};


例如,我在数据库中有这些用户

{
"_id" : ObjectId("5aca2ac25c1d8adeb4a2dab0"),
first_name:"Pierro",
last_name:"pierre",
sex:"H",
fk_hobbies: [
{
"_id" : ObjectId("5ac9f84d5c1f8adeb4a2da97"),
"name" : "Art"
},
{
"_id" : ObjectId("5ac9f84d5c8d8adeb4a2da97"),
"name" : "Sport"
},
{
"_id" : ObjectId("5ac9f84d9c1d8adeb4a2da97"),
"name" : "Fete"
},
{
"_id" : ObjectId("5acaf84d5c1d8adeb4a2da97"),
"name" : "Série"
},
{
"_id" : ObjectId("6ac9f84d5c1d8adeb4a2da97"),
"name" : "Jeux vidéo"
}
]
},
{
"_id" : ObjectId("5ac9fa075c1d8adeb4a2da99"),
first_name:"jean",
last_name:"mark",
sex:"H",
fk_hobbies: [
{
"_id" : ObjectId("5ac7f84d5c1d8adeb4a2da97"),
"name" : "Musique"
},
{
"_id" : ObjectId("5ac9f24d5c1d8adeb4a2da97"),
"name" : "Chiller"
},
{
"_id" : ObjectId("5ac9f84c5c1d8adeb4a2da97"),
"name" : "Papoter"
},
{
"_id" : ObjectId("5ac9f84d2c1d8adeb4a2da97"),
"name" : "Manger"
},
{
"_id" : ObjectId("5ac9f84d5c1d8adeb4a2da97"),
"name" : "Film"
}
]
},
{
"_id" : ObjectId("5aca0a635c1d8adeb4a2da9d"),
first_name:"michael",
last_name:"ferrari",
sex:"H",
fk_hobbies: [
{
"_id" : ObjectId("5ac9f84d5c1d8adeb4a2ea97"),
"name" : "fashion"
},
{
"_id" : ObjectId("5ac9f84d5c1e8adeb4a2da97"),
"name" : "Voyage"
},
{
"_id" : ObjectId("5ac9f84c5c1d8adeb4a2da97"),
"name" : "Papoter"
},
{
"_id" : ObjectId("5ac9f84d2c1d8adeb4a2da97"),
"name" : "Manger"
},
{
"_id" : ObjectId("5ac9f84d5c1d8adeb4a2da97"),
"name" : "Film"
}
]
},
{
"_id" : ObjectId("5ac9fa074c1d8adeb4a2da99"),
first_name:"Philip",
last_name:"roi",
sex:"H",
fk_hobbies:
[
{
"_id" : ObjectId("5ac7f84d5c1d8adeb4a2da97"),
"name" : "Musique"
},
{
"_id" : ObjectId("5ac9f24d5c1d8adeb4a2da97"),
"name" : "Chiller"
},
{
"_id" : ObjectId("5ac9f84c5c1d8adeb4a2da97"),
"name" : "Papoter"
},
{
"_id" : ObjectId("5ac9f84d2c1d8adeb4a2da97"),
"name" : "Manger"
},
{
"_id" : ObjectId("5ac9f84d5c1d8adeb4a2da97"),
"name" : "Film"
}
]
}


我想创建一个猫鼬查询,该查询将通过id获取的用户与数据库中的其他用户匹配,如下所示:
查询将首先返回具有相同兴趣爱好的最大数量(即5)的用户,然后返回具有相同4个兴趣爱好的用户...

我创建了一个完整的Javascipt / node js解决方案,mongo是否有任何查询?
这是我的解决方案

//var user : the current user that search other similar users : jean mark : 5ac9fa075c1d8adeb4a2da99
//var users : all other users
var tab = []


async.each(users, function(item, next1){
var j = 0;
var hobbies = item["fk_hobbies"]


for(var i = 0; i < 5; i++)
{
var index = hobbies.findIndex(x => x["_id"] == user[0]["fk_hobbies"][i]["_id"].toString());




if(index != -1)
j++
}

if(j != 0)
tab.push({nbHob:j, user:item})

next1()
}, function ()
{
var tab2 = tab.sort(compare)
res.json({success:true, data:tab2})
})


function compare(a,b) {
if (a.nbHob > b.nbHob)
return -1;
if (a.nbHob < b.nbHob)
return 1;
return 0;
}


显示的结果是这样的
nbHob:代表相似爱好的数量

{"success":true,"data":[{"nbHob":5,"user":{"_id":"5ac9fa074c1d8adeb4a2da99","u_first_name":"Akram","u_last_name":"Cherif","u_email":"","u_login":"","u_password":"","u_user_type":0,"u_date_of_birth":"","u_civility":0,"u_sex":"H","u_phone_number":"","u_facebook_id":"","u_google_id":"","u_twitter_id":"","u_profile_image":"","u_about":"","u_profession":"","u_fk_additional_infos":[null],"u_budget":0,"u_address":{"country":"France","state":"Paris","city":"TM","zip":76001},"u_fk_hobbies":[{"name":"Musique","_id":"5ac7f84d5c1d8adeb4a2da97"},{"name":"Chiller","_id":"5ac9f24d5c1d8adeb4a2da97"},{"name":"Papoter","_id":"5ac9f84c5c1d8adeb4a2da97"},{"name":"Manger","_id":"5ac9f84d2c1d8adeb4a2da97"},{"name":"Film","_id":"5ac9f84d5c1d8adeb4a2da97"}]}},{"nbHob":3,"user":{"_id":"5aca0a635c1d8adeb4a2da9d","u_first_name":"Chawki","u_last_name":"Gasmi","u_email":"","u_login":"","u_password":"","u_user_type":0,"u_date_of_birth":"","u_civility":0,"u_sex":"H","u_phone_number":"","u_facebook_id":"","u_google_id":"","u_twitter_id":"","u_profile_image":"","u_about":"","u_profession":"","u_fk_additional_infos":[null],"u_budget":{"min":500,"max":850},"u_address":{"country":"","state":"","city":"","zip":0},"u_fk_hobbies":[{"name":"fashion","_id":"5ac9f84d5c1d8adeb4a2ea97"},{"name":"Voyage","_id":"5ac9f84d5c1e8adeb4a2da97"},{"name":"Papoter","_id":"5ac9f84c5c1d8adeb4a2da97"},{"name":"Manger","_id":"5ac9f84d2c1d8adeb4a2da97"},{"name":"Film","_id":"5ac9f84d5c1d8adeb4a2da97"}]}}]}

最佳答案

您的问题数据似乎有点混乱,这可能是因为自由复制/粘贴的范围很广,因为每个爱好都具有相同的ObjectId值。但是我可以用一个完整的自包含示例来纠正这一问题:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/people';

mongoose.Promise = global.Promise;
mongoose.set('debug', true);

const hobbySchema = new Schema({
name: String
});

const userSchema = new Schema({
first_name: String,
last_name: String,
sex: String,
fk_hobbies: [hobbySchema]
});

const Hobby = mongoose.model('Hobby', hobbySchema)
const User = mongoose.model('User', userSchema);

const userData = [
{
"first_name" : "Pierro",
"last_name" : "pierre",
"sex" : "H",
"fk_hobbies" : [
"Art", "Sport", "Fete", "Série", "Jeux vidéo"
]
},
{
"first_name": "jean",
"last_name" : "mark",
"sex" : "H",
"fk_hobbies" : [
"Musique", "Chiller", "Papoter", "Manger", "Film"
]
},
{
"first_name" : "michael",
"last_name" : "ferrari",
"sex" : "H",
"fk_hobbies" : [
"fashion", "Voyage", "Papoter", "Manger", "Film"
]
},
{
"first_name" : "Philip",
"last_name" : "roi",
"sex" : "H",
"fk_hobbies" : [
"Musique", "Chiller", "Papoter", "Manger", "Film"
]
}
];

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

try {

const conn = await mongoose.connect(uri);

await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.remove())
);

const hobbies = await Hobby.insertMany(
[
...userData
.reduce((o, u) => [ ...o, ...u.fk_hobbies ], [])
.reduce((o, u) => o.set(u,1) , new Map())
]
.map(([name,v]) => ({ name }))
);

const users = await User.insertMany(userData.map(u =>
({
...u,
fk_hobbies: u.fk_hobbies.map(f => hobbies.find(h => f === h.name))
})
));

let user = await User.findOne({
"first_name" : "Philip",
"last_name" : "roi"
});
let user_hobbies = user.fk_hobbies.map(h => h._id );

let result = await User.aggregate([
{ "$match": {
"_id": { "$ne": user._id },
"fk_hobbies._id": { "$in": user_hobbies }
}},
{ "$addFields": {
"numHobbies": {
"$size": {
"$setIntersection": [
"$fk_hobbies._id",
user_hobbies
]
}
},
"fk_hobbies": {
"$map": {
"input": "$fk_hobbies",
"in": {
"$mergeObjects": [
"$$this",
{
"shared": {
"$cond": {
"if": { "$in": [ "$$this._id", user_hobbies ] },
"then": true,
"else": "$$REMOVE"
}
}
}
]
}
}
}
}},
{ "$sort": { "numHobbies": -1 } }
]);

log(result);

mongoose.disconnect();

} catch(e) {

} finally {
process.exit();
}
})()


其中大多数只是“设置”以重新创建数据集,而简单地说,我们只是添加用户及其爱好,并按名称为每个“独特爱好”保留一个“独特”标识符。这可能是您实际上在问题中所要表达的意思,这是您应该遵循的模型。

有趣的部分全部在 .aggregate()语句中,这是我们“查询”然后“计数”匹配的爱好并启用“服务器”对结果进行排序然后返回给客户端的方式。

给定一个当前用户(并且您所包括的列表中的最后一个具有最有趣的匹配项),然后我们关注代码的这一部分:

    // Simulates getting the current user to compare against
let user = await User.findOne({
"first_name" : "Philip",
"last_name" : "roi"
});

// Just get the list of _id values from the current user for reference
let user_hobbies = user.fk_hobbies.map(h => h._id );

let result = await User.aggregate([
// Find all users not the current user with at least one of the hobbies
{ "$match": {
"_id": { "$ne": user._id },
"fk_hobbies._id": { "$in": user_hobbies }
}},
// Add the count of matches, "optionally" we are marking the matched
// hobbies in the array as well.
{ "$addFields": {
"numHobbies": {
"$size": {
"$setIntersection": [
"$fk_hobbies._id",
user_hobbies
]
}
},
"fk_hobbies": {
"$map": {
"input": "$fk_hobbies",
"in": {
"$mergeObjects": [
"$$this",
{
"shared": {
"$cond": {
"if": { "$in": [ "$$this._id", user_hobbies ] },
"then": true,
"else": "$$REMOVE"
}
}
}
]
}
}
}
}},
// Sort the results by the "most" hobbies, which is "descending" order
{ "$sort": { "numHobbies": -1 } }
]);


我已经为您评论了这些步骤,但让我们对其进行扩展。

首先,我们假设您已经通过任何方式从数据库中返回了当前用户。出于其余操作的目的,您对该用户的真正需求是“用户”本身的 _id,当然还有该用户选择的每个爱好的 _id值。我们可以按照此处所示进行快速的 .map()操作,但是为了便于参考,我们保留了一个副本,并且在其余代码中不再重复。

然后我们进入实际的聚合语句。第一个条件是 $match,它像带有所有相同运算符的标准查询表达式一样工作。我们需要这些查询条件中的两件事:


获取除当前用户之外的所有用户以供考虑;
且这些用户在同一爱好中至少包含一个匹配项(按 _id值)。


因此,“其他所有人”的条件实质上是为 $ne值的参数提供 _id“不等于”运算符,当然要与当前用户 _id进行比较。仅获取具有相同爱好的人的第二个条件是对 $in数组的 _id字段使用 fk_hobbies运算符。在MongoDB查询中,我们将其表示为 "$fk_hobbies._id"以便与“内部” _id属性值匹配。

$in运算符本身以“列表”作为参数,并比较提供给条件分配给该属性的列表中的每个值。 MongoDB本身并不关心 fk_hobbies是数组还是单个值,而只会在提供的列表中查找任何内容的匹配项。将 $in视为编写 $or的一种简短方式,除了不需要在每个条件上显式包含相同的属性名称。

现在,您已选择了正确的文档,并舍弃了没有相同爱好的任何用户,我们可以继续进行下一个阶段。还要注意,整个 $match认为只需要那些“匹配”用户是合乎逻辑的。如果您实际上想查看“所有用户”,包括那些“不匹配”的用户,那么您可以简单地省略整个 $match管道阶段。您的代码将丢弃任何未被计数的内容,因此该代码根本不会理会“必须”具有 0计数的任何内容。

$addFields阶段管道阶段是一种向结果中返回的文档“添加新字段”的快速方法。除了其他用户详细信息之外,这里您想要的主要输出是 "numHobbies",因此此管道阶段运算符是执行此操作的最佳方法,但是如果您使用的是MongoDB服务器较旧,则只需指定“全部除了要使用 $project的所有新字段之外,您还希望包含的字段。

为了“计数”共有的兴趣爱好,我们基本上使用两个聚合运算符,分别是 $setIntersection$size。这两个版本都应该在您确实应该在生产环境中使用的MongoDB版本中可用。

按照各自的顺序, $setIntersection运算符“比较集”,在这种情况下,这是 _idfk_hobbies值的列表,既来自我们先前存储的当前选定用户,又来自表达式中考虑的当前文档。该运算符的结果是两个列表之间的“相同”值列表。

自然, $size运算符查看 $setIntersection中返回的列表(或set)并返回该列表中的条目数。这当然是“匹配计数”。

下一部分涉及投影 fk_hobbies数组的“重写”形式。这完全是可选的,由我自己设计用于演示。 “如果”您也想做我在这里所做的事情,那么这段代码要做的是在 fk_hobbies数组的对象中添加一个附加属性,以指示该特定爱好是与列表匹配的地方之一。

我说这是“可选的”,因为实际上是在演示仅适用于MongoDB 3.6的两个功能。这些涉及内部数组元素上 $mergeObjects的用法和 Conditionally Exlcuding Fields的用法。

逐步完成操作,因为 fk_hobbies是一个数组,我们需要使用 $map运算符来“重塑”其中的对象。此运算符使我们能够处理每个数组成员,并根据我们包含为参数的转换返回一个新值。它的用法与 .map()用于JavaScript或实现类似操作的任何其他语言的用法非常相似。

因此,对于数组( $$this)中的每个对象,我们应用 $mergeObjects运算符,该运算符将“合并”其参数的结果。这些已作为当前对象的 $$this提供,并且是表达式中的第二个自变量,正在做一些新的有趣的事情。

在这里,我们使用 $cond运算符,它是“三元”运算符(或 if..then..else表达式),它考虑条件 if然后返回表达式为true的 then参数或 else表达错误的地方。这里的表达式是 $in的另一种形式,用作聚合表达式。这种形式的第一个参数是一个奇异值 $$this._id,它将与第二个参数中的列表表达式进行比较。第二个参数当然是我们先前保存的当前用户爱好ID的列表,并再次用于比较。

单独使用 $in会在匹配的情况下返回 truefalse。但是这里展示的额外动作是,在 $cond表达式内,我们对 elsefalse条件返回新的特殊 $$REMOVE值。这意味着,通过我们的 "shared"属性,我们将添加到数组中的每个对象,而不是为其分配不匹配的 false值,实际上我们不在输出文档中包含该属性,即所有。

该“可选”部分实际上只是一个“很好的接触”,用于指示条件中匹配的“爱好”,而不是简单地返回计数。如果您喜欢它,请使用它,并且如果您没有具有这些功能的MongoDB 3.6,则无论如何都可以简单地对聚合输出返回的文档进行相同的更改:

    let result = await User.aggregate([
{ "$match": {
"_id": { "$ne": user._id },
"fk_hobbies._id": { "$in": user_hobbies }
}},
{ "$addFields": {
"numHobbies": {
"$size": {
"$setIntersection": [
"$fk_hobbies._id",
user_hobbies
]
}
}
}},
{ "$sort": { "numHobbies": -1 } }
]);

// map each result after return
result = result.map(r =>
({
...r,
fk_hobbies: r.fk_hobbies.map(h =>
({
...h,
...(( user_hobbies.map(i => i.toString() ).indexOf( h._id.toString() ) != -1 )
? { "shared": true } : {} )
})
)
})
)


无论哪种方式,您要从任何 $addFields$project语句中获取的主要内容是指示计数的实际 "numHobbies"值。我们在服务器上执行此操作的主要原因是,我们也可以在服务器上使用 $sort,这又允许您将 $limit$skip之类的内容添加到较大的结果集中,以便在其中分页。即使从初始匹配项或常规查询中对结果进行过滤,也很难从集合中获取所有结果。

无论如何,从同样在示例清单中生成的问题文档的小样本中,我们得到如下结果:

[
{
"_id": "5ad6bbe63365bc3428feed8a",
"first_name": "jean",
"last_name": "mark",
"sex": "H",
"fk_hobbies": [
{
"_id": "5ad6bbe63365bc3428feed7d",
"name": "Musique",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed7e",
"name": "Chiller",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed7f",
"name": "Papoter",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed80",
"name": "Manger",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed81",
"name": "Film",
"__v": 0,
"shared": true
}
],
"__v": 0,
"numHobbies": 5
},
{
"_id": "5ad6bbe63365bc3428feed90",
"first_name": "michael",
"last_name": "ferrari",
"sex": "H",
"fk_hobbies": [
{
"_id": "5ad6bbe63365bc3428feed82",
"name": "fashion",
"__v": 0
},
{
"_id": "5ad6bbe63365bc3428feed83",
"name": "Voyage",
"__v": 0
},
{
"_id": "5ad6bbe63365bc3428feed7f",
"name": "Papoter",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed80",
"name": "Manger",
"__v": 0,
"shared": true
},
{
"_id": "5ad6bbe63365bc3428feed81",
"name": "Film",
"__v": 0,
"shared": true
}
],
"__v": 0,
"numHobbies": 3
}
]


因此,有两个用户被返回,我们将匹配的兴趣爱好分别计为 53,并首先返回最匹配的爱好。您还可以在每个匹配的兴趣爱好上看到 "shared"属性的添加,以指示每个返回的用户列表中的哪些兴趣爱好也与与其进行比较的原始用户共享。




  注意:您可能只是在“尝试”,但实际上并不需要在问题中使用 async.each(),因为内部代码实际上都不是“异步”本身。即使在此处的清单中,在让当前用户进行比较之后,您实际上真正需要“等待”作为异步调用的唯一事情是 .aggregate()响应本身。
  
  因此,如果您在其中的任何部分都以为自己将“在一个循环中等待请求”,那么您就错了。只需向数据库查询结果,然后等待它们返回即可。
  
  只需向数据库发出一个请求。
  
  N.B也是2018年,所以您真的应该开始了解Promise和 async/await的用法。这样的代码就更简洁了,并且任何新开发的应用程序肯定都应该在具有此支持的环境中运行。因此,像“节点异步”之类的“回调帮助程序”库有点“老派”,在现代环境中已过时。

关于javascript - 对数组交叉点进行计数和排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49882953/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com