आपको अपने अंतिम परिणाम के लिए यहां कुछ चीजें करने की आवश्यकता है, लेकिन पहले चरण अपेक्षाकृत सरल हैं। आपके द्वारा प्रदान की जाने वाली उपयोगकर्ता वस्तु लें:
var user = {
user_id : 1,
Friends : [3,5,6],
Artists : [
{artist_id: 10 , weight : 345},
{artist_id: 17 , weight : 378}
]
};
अब मान लें कि आपके पास पहले से ही वह डेटा पुनर्प्राप्त है, तो यह प्रत्येक "मित्र" के लिए समान संरचनाओं को खोजने और "कलाकारों" की सरणी सामग्री को एक अलग सूची में फ़िल्टर करने के लिए नीचे आता है। संभवत:यहां प्रत्येक "वजन" पर भी कुल विचार किया जाएगा।
यह एक सरल एकत्रीकरण ऑपरेशन है जो पहले दिए गए उपयोगकर्ता के लिए सूची में पहले से मौजूद कलाकारों को फ़िल्टर करेगा:
var artists = user.Artists.map(function(artist) { return artist.artist_id });
User.aggregate(
[
// Find possible friends without all the same artists
{ "$match": {
"user_id": { "$in": user.Friends },
"Artists.artist_id": { "$nin": artists }
}},
// Pre-filter the artists already in the user list
{ "$project":
"Artists": {
"$setDifference": [
{ "$map": {
"input": "$Artists",
"as": "$el",
"in": {
"$cond": [
"$anyElementTrue": {
"$map": {
"input": artists,
"as": "artist",
"in": { "$eq": [ "$$artist", "$el.artist_id" ] }
}
},
false,
"$$el"
]
}
}}
[false]
]
}
}},
// Unwind the reduced array
{ "$unwind": "$Artists" },
// Group back by each artist and sum weights
{ "$group": {
"_id": "$Artists.artist_id",
"weight": { "$sum": "$Artists.weight" }
}},
// Sort the results by weight
{ "$sort": { "weight": -1 } }
],
function(err,results) {
// more to come here
}
);
"प्री-फ़िल्टर" यहाँ एकमात्र वास्तव में मुश्किल हिस्सा है। आप बस $unwind
कर सकते हैं
सरणी और $match
उन प्रविष्टियों को फिर से फ़िल्टर करने के लिए जिन्हें आप नहीं चाहते हैं। भले ही हम $unwind
. करना चाहते हैं परिणाम बाद में उन्हें संयोजित करने के लिए, यह उन्हें "पहले" सरणी से निकालने के लिए अधिक कुशल तरीके से काम करता है, इसलिए विस्तार करने के लिए कम है।
तो यहाँ $map
ऑपरेटर उपयोगकर्ता "कलाकार" सरणी के प्रत्येक तत्व के निरीक्षण की अनुमति देता है और फ़िल्टर किए गए "उपयोगकर्ता" कलाकारों की सूची के साथ तुलना के लिए केवल वांछित विवरण वापस करने की अनुमति देता है। $setDifference
वास्तव में किसी भी परिणाम को "फ़िल्टर" करने के लिए उपयोग किया जाता है जो सरणी सामग्री के रूप में वापस नहीं किया गया था, बल्कि false
के रूप में लौटाया गया था ।
उसके बाद बस $unwind
. है सरणी में सामग्री को सामान्य करने के लिए और $group
प्रति कलाकार कुल मिलाकर लाने के लिए। मनोरंजन के लिए हम $sort
. का उपयोग कर रहे हैं यह दिखाने के लिए कि सूची वांछित क्रम में लौटा दी गई है, लेकिन बाद में इसकी आवश्यकता नहीं होगी।
यह यहां के रास्ते का कम से कम हिस्सा है क्योंकि परिणामी सूची में केवल अन्य कलाकार होने चाहिए जो पहले से ही उपयोगकर्ता की अपनी सूची में नहीं हैं, और किसी भी कलाकार से संक्षेप में "वजन" द्वारा क्रमबद्ध किया जाना चाहिए जो संभवतः एकाधिक मित्रों पर दिखाई दे सकता है।
श्रोताओं की संख्या को ध्यान में रखने के लिए अगले भाग को "कलाकारों" संग्रह से डेटा की आवश्यकता होगी। जबकि नेवले में एक .populate()
होता है विधि, आप वास्तव में इसे यहां नहीं चाहते हैं क्योंकि आप "विशिष्ट उपयोगकर्ता" गणना की तलाश में हैं। इसका तात्पर्य प्रत्येक कलाकार के लिए अलग-अलग गणना प्राप्त करने के लिए एक और एकत्रीकरण कार्यान्वयन है।
पिछले एकत्रीकरण कार्रवाई की परिणाम सूची के बाद, आप $_id
का उपयोग करेंगे इस तरह के मान:
// First get just an array of artist id's
var artists = results.map(function(artist) {
return artist._id;
});
Artist.aggregate(
[
// Match artists
{ "$match": {
"artistID": { "$in": artists }
}},
// Project with weight for distinct users
{ "$project": {
"_id": "$artistID",
"weight": {
"$multiply": [
{ "$size": {
"$setUnion": [
{ "$map": {
"input": "$user_tag",
"as": "tag",
"in": "$$tag.user_id"
}},
[]
]
}},
10
]
}
}}
],
function(err,results) {
// more later
}
);
यहाँ ट्रिक कुल मिलाकर $map
. के साथ की जाती है मूल्यों का एक समान परिवर्तन करने के लिए जो $setUnion
उन्हें एक अनूठी सूची बनाने के लिए। फिर $size
ऑपरेटर को यह पता लगाने के लिए लागू किया जाता है कि वह सूची कितनी बड़ी है। अतिरिक्त गणित उस संख्या को कुछ अर्थ देना है जब पिछले परिणामों से पहले से दर्ज वजन के खिलाफ लागू किया जाता है।
बेशक आपको इन सभी को किसी न किसी तरह से एक साथ लाने की जरूरत है, क्योंकि अभी परिणामों के केवल दो अलग-अलग सेट हैं। मूल प्रक्रिया एक "हैश टेबल" है, जहां अद्वितीय "कलाकार" आईडी मान कुंजी के रूप में उपयोग किए जाते हैं और "वजन" मान संयुक्त होते हैं।
आप इसे कई तरीकों से कर सकते हैं, लेकिन चूंकि संयुक्त परिणामों को "क्रमबद्ध" करने की इच्छा है, तो मेरी प्राथमिकता कुछ "मोंगोडीबीश" होगी क्योंकि यह उन बुनियादी तरीकों का पालन करती है जिनका आपको पहले से उपयोग किया जाना चाहिए।
इसे लागू करने का एक आसान तरीका nedb
का उपयोग करना है
, जो एक "मेमोरी में" स्टोर प्रदान करता है जो मोंगोडीबी संग्रहों को पढ़ने और लिखने के लिए उपयोग की जाने वाली समान प्रकार की विधियों का उपयोग करता है।
यदि आपको बड़े परिणामों के लिए वास्तविक संग्रह का उपयोग करने की आवश्यकता है, तो यह भी अच्छी तरह से मापता है, क्योंकि सभी सिद्धांत समान रहते हैं।
-
पहला एकत्रीकरण ऑपरेशन स्टोर में नया डेटा सम्मिलित करता है
-
दूसरा एकत्रीकरण "अपडेट" करता है कि डेटा "वजन" फ़ील्ड को बढ़ाता है
एक पूर्ण कार्य सूची के रूप में, और async
की कुछ अन्य सहायता से
पुस्तकालय यह इस तरह दिखेगा:
function GetUserRecommendations(userId,callback) {
var async = require('async')
DataStore = require('nedb');
User.findOne({ "user_id": user_id},function(err,user) {
if (err) callback(err);
var artists = user.Artists.map(function(artist) {
return artist.artist_id;
});
async.waterfall(
[
function(callback) {
var pipeline = [
// Find possible friends without all the same artists
{ "$match": {
"user_id": { "$in": user.Friends },
"Artists.artist_id": { "$nin": artists }
}},
// Pre-filter the artists already in the user list
{ "$project":
"Artists": {
"$setDifference": [
{ "$map": {
"input": "$Artists",
"as": "$el",
"in": {
"$cond": [
"$anyElementTrue": {
"$map": {
"input": artists,
"as": "artist",
"in": { "$eq": [ "$$artist", "$el.artist_id" ] }
}
},
false,
"$$el"
]
}
}}
[false]
]
}
}},
// Unwind the reduced array
{ "$unwind": "$Artists" },
// Group back by each artist and sum weights
{ "$group": {
"_id": "$Artists.artist_id",
"weight": { "$sum": "$Artists.weight" }
}},
// Sort the results by weight
{ "$sort": { "weight": -1 } }
];
User.aggregate(pipeline, function(err,results) {
if (err) callback(err);
async.each(
results,
function(result,callback) {
result.artist_id = result._id;
delete result._id;
DataStore.insert(result,callback);
},
function(err)
callback(err,results);
}
);
});
},
function(results,callback) {
var artists = results.map(function(artist) {
return artist.artist_id; // note that we renamed this
});
var pipeline = [
// Match artists
{ "$match": {
"artistID": { "$in": artists }
}},
// Project with weight for distinct users
{ "$project": {
"_id": "$artistID",
"weight": {
"$multiply": [
{ "$size": {
"$setUnion": [
{ "$map": {
"input": "$user_tag",
"as": "tag",
"in": "$$tag.user_id"
}},
[]
]
}},
10
]
}
}}
];
Artist.aggregate(pipeline,function(err,results) {
if (err) callback(err);
async.each(
results,
function(result,callback) {
result.artist_id = result._id;
delete result._id;
DataStore.update(
{ "artist_id": result.artist_id },
{ "$inc": { "weight": result.weight } },
callback
);
},
function(err) {
callback(err);
}
);
});
}
],
function(err) {
if (err) callback(err); // callback with any errors
// else fetch the combined results and sort to callback
DataStore.find({}).sort({ "weight": -1 }).exec(callback);
}
);
});
}
इसलिए प्रारंभिक स्रोत उपयोगकर्ता ऑब्जेक्ट से मिलान करने के बाद मानों को पहले कुल फ़ंक्शन में पास किया जाता है, जो श्रृंखला में निष्पादित हो रहा है और async.waterfall
का उपयोग कर रहा है। इसका परिणाम पास करने के लिए।
ऐसा होने से पहले हालांकि एकत्रीकरण के परिणाम DataStore
. में जोड़े जाते हैं नियमित .insert()
. के साथ कथन, _id
. का नाम बदलने का ध्यान रखते हुए फ़ील्ड nedb
. के रूप में अपने आप उत्पन्न _id
. के अलावा कुछ भी पसंद नहीं है मूल्य। प्रत्येक परिणाम artist_id
. के साथ डाला जाता है और weight
एकत्रीकरण परिणाम से गुण।
उस सूची को फिर दूसरे एकत्रीकरण ऑपरेशन में भेज दिया जाता है जो प्रत्येक निर्दिष्ट "कलाकार" को विशिष्ट उपयोगकर्ता आकार के आधार पर गणना किए गए "वजन" के साथ वापस करने जा रहा है। एक ही .update()
. के साथ "अपडेटेड" हैं DataStore
. पर स्टेटमेंट प्रत्येक कलाकार के लिए और "वज़न" फ़ील्ड में वृद्धि करना।
सब ठीक चल रहा है, अंतिम ऑपरेशन .find()
. करना है वे परिणाम और .sort()
उन्हें संयुक्त "वजन" द्वारा, और परिणाम को फ़ंक्शन में पास किए गए कॉलबैक में वापस कर दें।
तो आप इसे इस तरह इस्तेमाल करेंगे:
GetUserRecommendations(1,function(err,results) {
// results is the sorted list
});
और यह उन सभी कलाकारों को वापस करने जा रहा है जो वर्तमान में उस उपयोगकर्ता की सूची में नहीं हैं, बल्कि उनकी मित्र सूची में हैं और मित्र के सुनने की संख्या के संयुक्त भार और उस कलाकार के अलग-अलग उपयोगकर्ताओं की संख्या से स्कोर द्वारा आदेशित हैं।
इस तरह आप दो अलग-अलग संग्रहों के डेटा से निपटते हैं जिन्हें आपको विभिन्न समेकित विवरणों के साथ एक परिणाम में संयोजित करने की आवश्यकता होती है। यह कई प्रश्न और एक कार्य स्थान है, लेकिन मोंगोडीबी दर्शन का भी हिस्सा है कि इस तरह के संचालन को डेटाबेस में "जॉइन" परिणामों के लिए फेंकने से बेहतर तरीके से किया जाता है।