Skip to content

Instantly share code, notes, and snippets.

@ricardobeat
Forked from malgorithms/recommender.coffee
Created December 20, 2011 19:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ricardobeat/1502809 to your computer and use it in GitHub Desktop.
Save ricardobeat/1502809 to your computer and use it in GitHub Desktop.
Fake Recommender class for CoffeeScript/tame discussion - using async (http://github.com/caolan/async)
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"
@ricardobeat
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment