Skip to content

Commit 0cdc980

Browse files
committed
Adding key.js methods
1 parent 746a70c commit 0cdc980

4 files changed

Lines changed: 101 additions & 62 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ raccoon.recommendFor('userId', 'numberOfRecs').then((results) => {
141141
// returns an ranked sorted array of itemIds which represent the top recommendations
142142
// for that individual user based on knn.
143143
// numberOfRecs is the number of recommendations you want to receive.
144-
// asking for recommendations queries the 'recommendedSet' sorted set for the user.
144+
// asking for recommendations queries the 'recommendedZSet' sorted set for the user.
145145
// the movies in this set were calculated in advance when the user last rated
146146
// something.
147147
// ex. results = ['batmanId', 'supermanId', 'chipmunksId']
148148
});
149149

150150
raccoon.mostSimilarUsers('userId').then((results) => {
151-
// returns an array of the 'similaritySet' ranked sorted set for the user which
151+
// returns an array of the 'similarityZSet' ranked sorted set for the user which
152152
// represents their ranked similarity to all other users given the
153153
// Jaccard Coefficient. the value is between -1 and 1. -1 means that the
154154
// user is the exact opposite, 1 means they're exactly the same.
@@ -166,7 +166,7 @@ raccoon.leastSimilarUsers('userId').then((results) => {
166166
#### Ratings:
167167
``` js
168168
raccoon.bestRated().then((results) => {
169-
// returns an array of the 'scoreBoard' sorted set which represents the global
169+
// returns an array of the 'scoreboard' sorted set which represents the global
170170
// ranking of items based on the Wilson Score Interval. in short it represents the
171171
// 'best rated' items based on the ratio of likes/dislikes and cuts out outliers.
172172
// ex. results = ['iceageId', 'sleeplessInSeattleId', 'theDarkKnightId']

lib/algorithms.js

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ const jaccardCoefficient = function(userId1, userId2, callback){
1414
finalJaccard = 0,
1515
ratedInCommon = 0;
1616

17-
const user1LikedSet = [CLASSNAME,'user',userId1,'liked'].join(":"),
18-
user1DislikedSet = [CLASSNAME,'user',userId1,'disliked'].join(":"),
19-
user2LikedSet = [CLASSNAME,'user',userId2,'liked'].join(":"),
20-
user2DislikedSet = [CLASSNAME,'user',userId2,'disliked'].join(":");
17+
const user1LikedSet = Key.userLikedSet(userId1),
18+
user1DislikedSet = Key.userDislikedSet(userId1),
19+
user2LikedSet = Key.userLikedSet(userId2),
20+
user2DislikedSet = Key.userDislikedSet(userId2);
2121

2222
// retrieving a set of the users likes incommon
2323
client.sinter(user1LikedSet,user2LikedSet, function(err, results1){
@@ -51,17 +51,17 @@ exports.updateSimilarityFor = function(userId, cb){
5151
// initializing variables
5252
let userRatedItemIds, itemLiked, itemDisliked, itemLikeDislikeKeys;
5353
// setting the redis key for the user's similarity set
54-
const similaritySet = Key.similaritySet(userId);
54+
const similarityZSet = Key.similarityZSet(userId);
5555
// creating a combined set with the all of a users likes and dislikes
56-
client.sunion([CLASSNAME,'user',userId,'liked'].join(":"),[CLASSNAME,'user',userId,'disliked'].join(":"), function(err, userRatedItemIds){
56+
client.sunion(Key.userLikedSet(userId), Key.userDislikedSet(userId), function(err, userRatedItemIds){
5757
// if they have rated anything
5858
if (userRatedItemIds.length > 0){
5959
// creating a list of redis keys to look up all of the likes and dislikes for a given set of items
6060
itemLikeDislikeKeys = _.map(userRatedItemIds, function(itemId, key){
6161
// key for that item being liked
62-
itemLiked = [CLASSNAME, 'item', itemId, 'liked'].join(":");
62+
itemLiked = Key.itemLikedBySet(itemId);
6363
// key for the item being disliked
64-
itemDisliked = [CLASSNAME, 'item', itemId, 'disliked'].join(":");
64+
itemDisliked = Key.itemDislikedBySet(itemId);
6565
// returning an array of those keys
6666
return [itemLiked, itemDisliked];
6767
});
@@ -85,7 +85,7 @@ exports.updateSimilarityFor = function(userId, cb){
8585
// similarity
8686
jaccardCoefficient(userId, otherUserId, function(result) {
8787
// with the returned similarity score, add it to a sorted set named above
88-
client.zadd(similaritySet, result, otherUserId, function(err){
88+
client.zadd(similarityZSet, result, otherUserId, function(err){
8989
// call the async callback function once finished to indicate that the process is finished
9090
callback();
9191
});
@@ -106,13 +106,13 @@ exports.predictFor = function(userId, itemId){
106106
itemId = String(itemId);
107107
let finalSimilaritySum = 0.0;
108108
let prediction = 0.0;
109-
const similaritySet = Key.similaritySet(userId);
110-
const likedBySet = [CLASSNAME, 'item', itemId, 'liked'].join(':');
111-
const dislikedBySet = [CLASSNAME, 'item', itemId, 'disliked'].join(':');
109+
const similarityZSet = Key.similarityZSet(userId);
110+
const likedBySet = Key.itemLikedBySet(itemId);
111+
const dislikedBySet = Key.itemDislikedBySet(itemId);
112112

113113
return new Promise((resolve, reject) => {
114-
exports.similaritySum(similaritySet, likedBySet, function(result1){
115-
exports.similaritySum(similaritySet, dislikedBySet, function(result2){
114+
exports.similaritySum(similarityZSet, likedBySet, function(result1){
115+
exports.similaritySum(similarityZSet, dislikedBySet, function(result2){
116116
finalSimilaritySum = result1 - result2;
117117
client.scard(likedBySet, function(err, likedByCount){
118118
client.scard(dislikedBySet, function(err, dislikedByCount){
@@ -159,30 +159,29 @@ exports.updateRecommendationsFor = function(userId, cb){
159159
let setsToUnion = [];
160160
let scoreMap = [];
161161
// initializing the redis keys for temp sets, the similarity set and the recommended set
162-
const tempSet = [CLASSNAME, userId, 'tempSet'].join(":");
163-
const tempDiffSet = [CLASSNAME, userId, 'tempDiffSet'].join(":");
164-
const similaritySet = Key.similaritySet(userId);
165-
const recommendedSet = Key.recommendedSet(userId);
162+
const tempAllLikedSet = Key.tempAllLikedSet(userId);
163+
const similarityZSet = Key.similarityZSet(userId);
164+
const recommendedZSet = Key.recommendedZSet(userId);
166165
// returns an array of the users that are most similar within k nearest neighbors
167-
client.zrevrange(similaritySet, 0, config.nearestNeighbors-1, function(err, mostSimilarUserIds){
166+
client.zrevrange(similarityZSet, 0, config.nearestNeighbors-1, function(err, mostSimilarUserIds){
168167
// returns an array of the users that are least simimilar within k nearest neighbors
169-
client.zrange(similaritySet, 0, config.nearestNeighbors-1, function(err, leastSimilarUserIds){
168+
client.zrange(similarityZSet, 0, config.nearestNeighbors-1, function(err, leastSimilarUserIds){
170169
// iterate through the user ids to create the redis keys for all those users likes
171-
_.each(mostSimilarUserIds, function(id, key){
172-
setsToUnion.push([CLASSNAME,'user',id,'liked'].join(":"));
170+
_.each(mostSimilarUserIds, function(usrId, key){
171+
setsToUnion.push(Key.userLikedSet(usrId));
173172
});
174173
// if you want to factor in the least similar least likes, you change this in config
175174
// left it off because it was recommending items that every disliked universally
176-
_.each(leastSimilarUserIds, function(id, key){
177-
setsToUnion.push([CLASSNAME,'user',id,'disliked'].join(":"));
175+
_.each(leastSimilarUserIds, function(usrId, key){
176+
setsToUnion.push(Key.userDislikedSet(usrId));
178177
});
179178
// if there is at least one set in the array, continue
180179
if (setsToUnion.length > 0){
181-
setsToUnion.unshift(tempSet);
180+
setsToUnion.unshift(tempAllLikedSet);
182181
client.sunionstore(setsToUnion, function(err) {
183182
// using the new array of all the items that were liked by people similar and disliked by people opposite, create a new set with all the
184183
// items that the current user hasn't already rated
185-
client.sdiff(tempSet, [CLASSNAME,'user',userId,'liked'].join(":"), [CLASSNAME,'user',userId,'disliked'].join(":"), function(err, notYetRatedItems){
184+
client.sdiff(tempAllLikedSet, Key.userLikedSet(userId), Key.userDislikedSet(userId), function(err, notYetRatedItems){
186185
// with the array of items that user has not yet rated, iterate through all of them and predict what the current user would rate it
187186
async.each(notYetRatedItems,
188187
function(itemId, callback){
@@ -195,18 +194,18 @@ exports.updateRecommendationsFor = function(userId, cb){
195194
// using score map which is an array of what the current user would rate all the unrated items,
196195
// add them to that users sorted recommended set
197196
function(err){
198-
client.del(recommendedSet, function(err){
197+
client.del(recommendedZSet, function(err){
199198
async.each(scoreMap,
200199
function(scorePair, callback){
201-
client.zadd(recommendedSet, scorePair[0], scorePair[1], function(err){
200+
client.zadd(recommendedZSet, scorePair[0], scorePair[1], function(err){
202201
callback();
203202
});
204203
},
205204
// after all the additions have been made to the recommended set,
206205
function(err){
207-
client.del(tempSet, function(err){
208-
client.zcard(recommendedSet, function(err, length){
209-
client.zremrangebyrank(recommendedSet, 0, length-config.numOfRecsStore-1, function(err){
206+
client.del(tempAllLikedSet, function(err){
207+
client.zcard(recommendedZSet, function(err, length){
208+
client.zremrangebyrank(recommendedZSet, 0, length-config.numOfRecsStore-1, function(err){
210209
cb();
211210
});
212211
});
@@ -229,9 +228,9 @@ exports.updateRecommendationsFor = function(userId, cb){
229228
// outliers. the wilson score is a value between 0 and 1.
230229
exports.updateWilsonScore = function(itemId, callback){
231230
// creating the redis keys for scoreboard and to get the items liked and disliked sets
232-
const scoreBoard = [CLASSNAME, 'scoreBoard'].join(":");
233-
const likedBySet = [CLASSNAME, 'item', itemId, 'liked'].join(':');
234-
const dislikedBySet = [CLASSNAME, 'item', itemId, 'disliked'].join(':');
231+
const scoreboard = Key.scoreboardZSet();
232+
const likedBySet = Key.itemLikedBySet(itemId);
233+
const dislikedBySet = Key.itemDislikedBySet(itemId);
235234
// used for a confidence interval of 95%
236235
const z = 1.96;
237236
// initializing variables to calculate wilson score
@@ -258,7 +257,7 @@ exports.updateWilsonScore = function(itemId, callback){
258257
score = 0.0;
259258
}
260259
// add that score to the overall scoreboard. if that item already exists, the score will be updated.
261-
client.zadd(scoreBoard, score, itemId, function(err){
260+
client.zadd(scoreboard, score, itemId, function(err){
262261
// call the final callback sent to the initial function.
263262
callback();
264263
});

lib/key.js

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

22
const config = require('./config.js');
33

4-
const CLASSNAME = config.className;
5-
const USER = 'user';
4+
const CLASSNAME = config.className,
5+
USER = 'user',
6+
ITEM = 'item';
67

78
class Key {
89
constructor() {
@@ -11,19 +12,58 @@ class Key {
1112
}
1213

1314
joinKey() {
14-
this.key = this.keyArr.join(':');
15+
this.key = [CLASSNAME].concat(this.keyArr).join(':');
16+
return this.key;
1517
}
1618

17-
similaritySet(userId) {
18-
this.keyArr = [CLASSNAME, USER, userId, 'similaritySet'];
19-
this.joinKey();
20-
return this.key;
19+
userLikedSet(userId) {
20+
this.keyArr = [USER, userId, 'liked'];
21+
return this.joinKey();
2122
}
2223

23-
recommendedSet(userId) {
24-
this.keyArr = [CLASSNAME, USER, userId, 'recommendedSet'];
25-
this.joinKey();
26-
return this.key;
24+
userDislikedSet(userId) {
25+
this.keyArr = [USER, userId, 'disliked'];
26+
return this.joinKey();
27+
}
28+
29+
itemLikedBySet(itemId) {
30+
this.keyArr = [ITEM, itemId, 'liked'];
31+
return this.joinKey();
32+
}
33+
34+
itemDislikedBySet(itemId) {
35+
this.keyArr = [ITEM, itemId, 'disliked'];
36+
return this.joinKey();
37+
}
38+
39+
mostLiked() {
40+
this.keyArr = ['mostLiked'];
41+
return this.joinKey();
42+
}
43+
44+
mostDisliked() {
45+
this.KeyArr = ['mostDisliked'];
46+
return this.joinKey();
47+
}
48+
49+
recommendedZSet(userId) {
50+
this.keyArr = [USER, userId, 'recommendedZSet'];
51+
return this.joinKey();
52+
}
53+
54+
scoreboardZSet() {
55+
this.keyArr = ['scoreboard'];
56+
return this.joinKey();
57+
}
58+
59+
similarityZSet(userId) {
60+
this.keyArr = [USER, userId, 'similarityZSet'];
61+
return this.joinKey();
62+
}
63+
64+
tempAllLikedSet(userId) {
65+
this.keyArr = [USER, userId, 'tempAllLikedSet'];
66+
return this.joinKey();
2767
}
2868
}
2969

lib/stat.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ const CLASSNAME = config.className;
88
const stat = {
99
recommendFor: function(userId, numberOfRecs){
1010
return new Promise((resolve, reject) => {
11-
client.zrevrangeAsync(Key.recommendedSet(userId), 0, numberOfRecs).then((results) => {
11+
client.zrevrangeAsync(Key.recommendedZSet(userId), 0, numberOfRecs).then((results) => {
1212
resolve(results);
1313
});
1414
});
1515
},
1616
bestRated: function(){
1717
return new Promise((resolve, reject) => {
18-
client.zrevrangeAsync([CLASSNAME,'scoreBoard'].join(":"), 0, -1).then((results) => {
18+
client.zrevrangeAsync(Key.scoreboardZSet(), 0, -1).then((results) => {
1919
resolve(results);
2020
});
2121
});
2222
},
2323
worstRated: function(){
2424
return new Promise((resolve, reject) => {
25-
client.zrangeAsync([CLASSNAME, 'scoreBoard'].join(":"), 0, -1).then((results) => {
25+
client.zrangeAsync(Key.scoreboardZSet(), 0, -1).then((results) => {
2626
resolve(results);
2727
});
2828
});
2929
},
3030
bestRatedWithScores: function(numOfRatings){
3131
return new Promise((resolve, reject) => {
32-
client.zrevrangeAsync([CLASSNAME,'scoreBoard'].join(":"), 0, numOfRatings, 'withscores').then((results) => {
32+
client.zrevrangeAsync(Key.scoreboardZSet(), 0, numOfRatings, 'withscores').then((results) => {
3333
resolve(results);
3434
});
3535
});
@@ -52,63 +52,63 @@ const stat = {
5252
},
5353
mostSimilarUsers: function(userId){
5454
return new Promise((resolve, reject) => {
55-
client.zrevrangeAsync(Key.similaritySet(userId), 0, -1).then((results) => {
55+
client.zrevrangeAsync(Key.similarityZSet(userId), 0, -1).then((results) => {
5656
resolve(results);
5757
});
5858
});
5959
},
6060
leastSimilarUsers: function(userId){
6161
return new Promise((resolve, reject) => {
62-
client.zrangeAsync([CLASSNAME, userId, 'similaritySet'].join(":"), 0, -1).then((results) => {
62+
client.zrangeAsync(Key.similarityZSet(userId), 0, -1).then((results) => {
6363
resolve(results);
6464
});
6565
});
6666
},
6767
likedBy: function(itemId){
6868
return new Promise((resolve, reject) => {
69-
client.smembersAsync([CLASSNAME,'item',itemId,'liked'].join(':')).then((results) => {
69+
client.smembersAsync(Key.itemLikedBySet(itemId)).then((results) => {
7070
resolve(results);
7171
});
7272
});
7373
},
7474
likedCount: function(itemId){
7575
return new Promise((resolve, reject) => {
76-
client.scardAsync([CLASSNAME,'item',itemId,'liked'].join(':')).then((results) => {
76+
client.scardAsync(Key.itemLikedBySet(itemId)).then((results) => {
7777
resolve(results);
7878
});
7979
});
8080
},
8181
dislikedBy: function(itemId){
8282
return new Promise((resolve, reject) => {
83-
client.smembersAsync([CLASSNAME,'item',itemId,'disliked'].join(':')).then((results) => {
83+
client.smembersAsync(Key.itemDislikedBySet(itemId)).then((results) => {
8484
resolve(results);
8585
});
8686
});
8787
},
8888
dislikedCount: function(itemId){
8989
return new Promise((resolve, reject) => {
90-
client.scardAsync([CLASSNAME,'item',itemId,'disliked'].join(':')).then((results) => {
90+
client.scardAsync(Key.itemDislikedBySet(itemId)).then((results) => {
9191
resolve(results);
9292
});
9393
});
9494
},
9595
allLikedFor: function(userId){
9696
return new Promise((resolve, reject) => {
97-
client.smembersAsync([CLASSNAME, 'user', userId, 'liked'].join(":")).then((results) => {
97+
client.smembersAsync(Key.userLikedSet(userId)).then((results) => {
9898
resolve(results);
9999
});
100100
});
101101
},
102102
allDislikedFor: function(userId){
103103
return new Promise((resolve, reject) => {
104-
client.smembersAsync([CLASSNAME, 'user', userId, 'disliked'].join(":")).then((results) => {
104+
client.smembersAsync(Key.userDislikedSet(userId)).then((results) => {
105105
resolve(results);
106106
});
107107
});
108108
},
109109
allWatchedFor: function(userId){
110110
return new Promise((resolve, reject) => {
111-
client.sunionAsync([CLASSNAME, 'user', userId, 'liked'].join(":"), [CLASSNAME, 'user', userId, 'disliked'].join(":")).then((results) => {
111+
client.sunionAsync(Key.userLikedSet(userId), Key.userDislikedSet(userId)).then((results) => {
112112
resolve(results);
113113
});
114114
});

0 commit comments

Comments
 (0)