public
Last active — forked from malgorithms/recommender.coffee

Fake Recommender class for CoffeeScript/tame discussion - using async (http://github.com/caolan/async)

  • Download Gist
recommender.coffee
CoffeeScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
async = require 'async'
 
class Recommender
getRecommendations: (search_params, respond) ->
interests = search_params.interests.slice()
interest_meta = {}
logged_in = user_info = user_taste_profile = null
taste_profiles = {}
full_recommendations = []
 
# Do 2 things at once:
# - check if we have a logged in user, get their info
# - fire off distributed requests for search queries
# ------------------------------------------------------------------------
step_one = (next) =>
async.parallel [
(fw) =>
@isLoggedIn ->
[logged_in, user_info] = arguments
fw null
(fw) =>
q = async.queue ((text, cb) => @getMetaInfo text, cb), 20
q.drain = fw
for interest in interests
q.push interest.text, (info) -> interest_meta[interest.text] = info
], next
 
#
# Do more things at once:
# - get a taste profile for the user (only if logged in)
# - get taste profiles for each legit search interest
# ------------------------------------------------------------------------
step_two = (next) =>
async.parallel [
(fw) =>
return fw null unless logged_in
@getUserTasteProfile user_info.id, (p) ->
user_taste_profile = p
fw null
(fw) =>
q = async.queue ((id, cb) => @getTasteProfile id, cb), 20
q.drain = fw
for own key, interest of interests
q.push interest.id, (err, p) -> taste_profiles[interest.id] = p
], next
 
#
# We have recs, but just [id, score] pairs. Let's:
# - look up info on each interest
# - save that this user got these recs (if logged in)
# ------------------------------------------------------------------------
step_three = =>
@getRecsFromTasteProfiles taste_profiles, user_taste_profile, (recommendations) =>
async.waterfall [
(fw) =>
q = async.queue ((info, cb) => @getInfo info, cb), 20
q.drain = fw
for id in recommendations
q.push id, (err, info) -> full_recommendations.push info
(fw) =>
return fw null unless logged_in
@rememberRecommendations user_info.id, recommendations, fw
]
async.series [step_one, step_two, step_three]
 
# --------------------------------------------------------------------------
 
 
# --------------------------------------------------------------------------
# Fake functions, for those who want to test getRecommendations()
# I added some management of what's concurrent
# --------------------------------------------------------------------------
 
isLoggedIn: (cb) ->
@_fakeRpc "logged_in", =>
if Math.random() < 0.5
cb true,
id: "user_id_#{Math.random()}"
age: Math.floor(18 + 30 * Math.random())
else
cb false, null
 
getRecsFromTasteProfiles: (tp, utp, cb) ->
@_fakeRpc "get_recs", =>
res = []
for i in [0..10]
res.push ["interest_id_#{Math.random()}", Math.random()]
cb res
 
getMetaInfo: (search_str, cb) ->
@_fakeRpc "get_meta", ->
cb id: "interest_id_#{Math.random()}"
 
getUserTasteProfile: (uid, cb) ->
@_fakeRpc "get_user_taste", ->
cb Math.random()
 
getTasteProfile: (uid, cb) ->
@_fakeRpc "get_taste", ->
cb Math.random()
 
getInfo: (rec_id, cb) ->
@_fakeRpc "get_info", ->
cb
title: "Bangin'"
avg_age: Math.floor(18 + 30 * Math.random())
id: rec_id
 
rememberRecommendations: (id, recommendations, cb) ->
@_fakeRpc "rememberRecommendations", cb
 
# --------------------------------------------------------------------------
 
_fakeRpc: (name, cb) ->
@_openRpcCount = {} if not @_openRpcCount
@_openRpcCount[name] = 0 if not @_openRpcCount[name]
@_openRpcCount[name]++
@_printFakeRpcData()
setTimeout((()=>
@_openRpcCount[name]--
@_printFakeRpcData()
cb()),Math.random()*1000)
 
_printFakeRpcData: ->
console.log "open remote calls: " + ("#{k} (#{v})" for k,v of @_openRpcCount).join(" ")
 
 
R = new Recommender()
 
search_params =
interests: [ { text: "football", opinion: 1.23 }, { text: "basketball", opinion: 13.23 } ]
 
start_time = Date.now()
 
R.getRecommendations search_params, (res) ->
console.log "-------------"
console.log "Done. Got #{res.length} results in #{Date.now() - start_time}ms"

could use async.waterfall instead of series to avoid the top-level variables.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.