Add popularity script master
authorGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Sat, 25 Apr 2009 15:41:37 +0000 (17:41 +0200)
committerGiuseppe Bilotta <giuseppe.bilotta@gmail.com>
Sat, 25 Apr 2009 15:41:37 +0000 (17:41 +0200)
vella_pop.rb [new file with mode: 0755]

diff --git a/vella_pop.rb b/vella_pop.rb
new file mode 100755 (executable)
index 0000000..43fe69f
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/ruby
+
+=begin
+What drives popularity? Are Pareto popularity distributions possible? likely?
+guaranteed?
+=end
+
+class Array
+       def pick_one
+               return nil if empty?
+               self[rand(self.length)]
+       end
+end
+
+class Popularity
+       # A Character has a name and nothing more
+       class Character
+               attr :name
+               def initialize(name)
+                       @name = name.intern
+               end
+       end
+
+       # A Star is just a Character, no special abilities
+       class Star < Character ; end
+
+       # A Commoner is a Character with one or more voting strategies
+       class Commoner < Character
+               # The simplest voting strategy: pick a random one
+               def random_vote(popularity, total)
+                       popularity.keys.pick_one
+               end
+
+               # A voting strategy that picks a random star, weighting
+               # more popular ones more
+               def popular_vote(popularity, total)
+                       # if nobody voted yet, pick a random one
+                       return random_vote(popularity, total) if total == 0
+                       pick = rand(total)
+                       popularity.each do |star, pop|
+                               return star if pick < pop
+                               pick -= pop
+                       end
+               end
+
+       end
+
+       # Add a new Star
+       def add_star(n)
+               case n
+               when String
+                       name = n
+               when Integer
+                       name = "Star #%u" % n
+               else
+                       raise ArgumentError, "#{n} is neither a String nor an Integer"
+               end
+
+               @stars[name.intern] = Star.new(name)
+       end
+
+       # Add a new Commoner
+       def add_commoner(n)
+               case n
+               when String
+                       name = n
+               when Integer
+                       name = "Commoner #%u" % n
+               else
+                       raise ArgumentError, "#{n} is neither a String nor an Integer"
+               end
+
+               @commoners[name.intern] = Commoner.new(name)
+       end
+
+       # Today
+       def today ; @day ; end
+       # Yesterday
+       def yesterday
+               raise if @day == 0
+               @day-1
+       end
+
+       # Initialize a new popularity day
+       def push_popularity
+               @popularity << Hash.new do |h, k|
+                       raise ArgumentError, "no such Star '#{k}'" unless @stars.key? k
+                       h[k] = []
+               end
+       end
+
+       def new_day
+               @day += 1
+               push_popularity
+       end
+
+       def initialize
+               @stars = {}
+               @commoners = {}
+               @popularity = []
+               reset
+       end
+
+       def reset
+               @day = 0
+               @popularity.clear
+               push_popularity
+       end
+
+       def stars ; @stars.keys ; end
+       def commoners ; @commoners.keys ; end
+
+       # Get :day's [default: today] popularity for :stars [default: all stars].
+       # Returns:
+               #   a hash star => number of commoners that voted for it that day
+       def popularity(o={})
+               pop = @popularity[o[:day] || today]
+               sel = {}
+               [o[:stars] || stars].flatten.each do |s|
+                       sel[s] = pop[s].length
+               end
+               return sel
+       end
+
+       # an Array of [star, popularity] ordered by popularity
+       def grade(o={})
+               popularity(o).to_a.sort { |s1, s2| s2.last <=> s1.last }
+       end
+
+       def vote(o = {})
+               pop_org = popularity(:day => yesterday)
+               # to allow giving stars with no popularity a chance,
+               # we allow a :mul and :add option to the voting session.
+               mul = o[:mul] || 1
+               add = o[:add] || 0
+               tot = 0
+               pop = {}
+               pop_org.each do |star, p|
+                       pop[star] = p*mul + add
+                       tot += pop[star]
+               end
+               @commoners.each do |name, commoner|
+                       star = commoner.popular_vote(pop, tot)
+                       @popularity[today][star] << name
+               end
+       end
+
+end
+
+pop = Popularity.new
+
+5.times do |i| pop.add_star(i) end
+100.times do |i| pop.add_commoner(i) end
+
+puts pop.grade.map { |s, p| "#{s} (#{p})" }.join(', ')
+
+puts
+1000.times do
+       pop.new_day
+       pop.vote
+       puts pop.grade.map { |s, p| "#{s} (#{p})" }.join(', ')
+end
+
+puts
+pop.reset
+1000.times do
+       pop.new_day
+       pop.vote(:add => 1)
+       puts pop.grade.map { |s, p| "#{s} (#{p})" }.join(', ')
+end
+
+puts
+pop.reset
+1000.times do
+       pop.new_day
+       pop.vote(:mul => 10, :add => 1)
+       puts pop.grade.map { |s, p| "#{s} (#{p})" }.join(', ')
+end
+