# from the book 'Programming Collective Intelligence' Toby Segran
#
module Recommendations

  Critics = {  'Lisa Rose' => { 'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.5, 'Just My Luck' => 3.0, 'Superman Returns' => 3.5, 'You, Me and Dupree' => 2.5, 'The Night Listener' => 3.0},
    'Gene Seymour' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 3.5, 'Just My Luck' => 1.5, 'Superman Returns' => 5.0, 'You, Me and Dupree' => 3.5, 'The Night Listener' => 3.0},
    'Michael Phillips' => { 'Lady in the Water' => 2.5, 'Snakes on a Plane' => 3.0, 'Superman Returns' => 3.5, 'The Night Listener' => 4.0},
    'Claudia Puig' => { 'Snakes on a Plane' => 3.5, 'Just My Luck' => 3.0, 'Superman Returns' => 4.0, 'You, Me and Dupree' => 2.5, 'The Night Listener' => 4.5},
    'Mick LaSalle' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0, 'Just My Luck' => 2.0, 'Superman Returns' => 3.0, 'You, Me and Dupree' => 2.0, 'The Night Listener' => 3.0},
    'Jack Mattews' => { 'Lady in the Water' => 3.0, 'Snakes on a Plane' => 4.0, 'Superman Returns' => 5.0, 'You, Me and Dupree' => 3.5, 'The Night Listener' => 3.0},
    'Toby' => { 'Snakes on a Plane' => 4.5, 'Superman Returns' => 4.0, 'You, Me and Dupree' => 1.0} 
  }

  def self.sim_distance(prefs, person1, person2)
    si = prefs[person1].keys.inject([]) do |r, item|
      if prefs[person2].key?(item)
        r << item 
      else
        r
      end
    end
    return 0 if si.empty?
    sum_of_squares = si.inject(0) do |r, item|
      r + (prefs[person1][item] - prefs[person2][item])**2
    end
    1 / (1 + sum_of_squares)
  end
  
  def self.sim_pearson(prefs, p1, p2)
    si = prefs[p1].keys.inject([]) do |r, item|
      if prefs[p2].key?(item)
        r << item 
      else
        r
      end
    end
    return 0 if si.empty?
    sum1 = prefs[p1].values_at(*si).inject(0) {|r, v| r + v }
    sum2 = prefs[p2].values_at(*si).inject(0) {|r, v| r + v }
    sum1sq = prefs[p1].values_at(*si).inject(0) {|r, v| r + v**2 }
    sum2sq = prefs[p2].values_at(*si).inject(0) {|r, v| r + v**2 }
    psum = prefs[p1].values_at(*si).zip(prefs[p2].values_at(*si)).inject(0) do |r, v|
      r + v[0] * v[1]
    end
    num = psum - (sum1 * sum2 / si.size)
    den = Math.sqrt((sum1sq - sum1**2 / si.size) * (sum2sq - sum2**2 / si.size))
    return 0 if den == 0
    num / den
  end
  
  def self.top_matches(prefs, person, n = 5, similarity = method(:sim_pearson))
    scores = prefs.keys.inject([]) do |r, other|
      if person == other
        r
      else
        r << [similarity.call(prefs, person, other), other]
      end
    end
    scores.sort!
    scores.reverse!
    scores[0...n]
  end
  
  def self.get_recommendations(prefs, person, similarity = method(:sim_pearson))
    totals = {}
    totals.default = 0.0
    simsums = {}
    simsums.default = 0.0
    prefs.keys.each do |other|
      next if other == person
      sim = similarity.call(prefs, person, other)
      next if sim <= 0
      prefs[other].each_key do |item|
        if !prefs[person].has_key?(item) || prefs[person][item] == 0
          totals[item] += prefs[other][item] * sim
          simsums[item] += sim
        end
      end
    end
    rankings = totals.map do |item, total|
      [total / simsums[item], item]
    end
    rankings.sort!
    rankings.reverse
  end
  
  def self.transform_prefs(prefs)
    result = Hash.new {|h, k| h[k] = {}}
    prefs.each_key do |person|
      prefs[person].each_key do |item|
        result[item][person] = prefs[person][item]
      end
    end
    result
  end
end
