From: Giuseppe Bilotta Date: Sat, 25 Apr 2009 15:41:37 +0000 (+0200) Subject: Add popularity script X-Git-Url: http://git.oblomov.eu/milivella/commitdiff_plain Add popularity script --- diff --git a/vella_pop.rb b/vella_pop.rb new file mode 100755 index 0000000..43fe69f --- /dev/null +++ b/vella_pop.rb @@ -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 +