[QUIZ] [Solution] Barrel of monkeys

Hello Group,

I once again had the pleasure of solving a ruby quiz. Here's my solution.

I implemeted a bidirectional search and a mechanism to devise more
general search evaluation functions. As an example I implemented the
search for the barrel of monkeys that best fills up a given total
duration.

I tried to make it work with unicode strings, but I'm not so shure
regexps are the way to go here :wink:

Because I'm a bit short on time (and shouldn't even have made this
quiz), I have not even implemented an user interface.

have fun,

Brian

Find everything nicely wrapped up at:
http://ruby.brian-schroeder.de/quiz/monkeysongs/

#!/usr/bin/ruby -Ku
require 'set'
require "rexml/document"
include REXML

class String
聽聽def uljust(n)
聽聽聽聽self + " " * [n - self.split(//).length, 0].max
聽聽end
end

class MonkeySong
聽聽attr_reader :artist, :name, :duration, :first_letter, :last_letter, :_id

聽聽def initialize(id, artist, name, duration)
聽聽聽聽@_id = id; @artist = artist; @name = name; @duration = duration
聽聽聽聽/^(.)/ =~ @name; @first_letter = $1.downcase
聽聽聽聽/(.)$/ =~ @name; @last_letter = $1.downcase
聽聽end

聽聽def to_s
聽聽聽聽"#{@name} - #{@artist} (#{@duration / 1000}s)"
聽聽end

聽聽def hash
聽聽聽聽@_id.hash
聽聽end

聽聽def eql?(o)
聽聽聽聽@_id == o._id
聽聽end
end

class MonkeySonglist < Array
聽聽def add_left_right(left, right)
聽聽聽聽(MonkeySonglist.new() << left).concat(self) << right
聽聽end

聽聽def to_s
聽聽聽聽self.inject('') do | r, song |
聽聽聽聽聽聽r << "#{song.name} - #{song.artist}".uljust(60) + "%2.2fm\n" %
(song.duration / 60000.0)
聽聽聽聽end << " " * 60 + "-----\n" % (self.duration / 60000.0) <<
聽聽聽聽" " * 60 + "%2.2fm\n" % (self.duration / 60000.0)
聽聽end

聽聽def duration
聽聽聽聽self.inject(0) { | r, s | r + s.duration }
聽聽end
end

class MonkeySongs
聽聽def initialize(library = 'SongLibrary.xml')
聽聽聽聽@starting_with = {}
聽聽聽聽@ending_with = {}
聽聽聽聽doc = Document.new(File.new(library))
聽聽聽聽@song_count = 0
聽聽聽聽doc.root.elements.each do | artist |
聽聽聽聽聽聽artist_name = artist.attributes['name']
聽聽聽聽聽聽artist.elements.each do | song |
聽聽song = MonkeySong.new(@song_count, artist_name,
song.attributes['name'], song.attributes['duration'].to_i)
聽聽(@starting_with[song.first_letter.downcase] ||= Set.new) << song
聽聽(@ending_with[song.last_letter.downcase] ||= Set.new) << song
聽聽@song_count += 1;
聽聽聽聽聽聽end
聽聽聽聽end
聽聽end

聽聽def find_any(from, to, max_depth = -1, used = Set.new)
聽聽聽聽return nil if max_depth == 0
聽聽聽聽starts = (@starting_with[from] || Set.new) - used
聽聽聽聽endings = (@ending_with[to] || Set.new) - used
聽聽聽聽return nil if starts.empty? or endings.empty?
聽聽聽聽connections = starts & endings
聽聽聽聽if !connections.empty? # Found connection
聽聽聽聽聽聽connections.each do |s| yield MonkeySonglist.new([s]) end
聽聽聽聽end
聽聽聽聽starts.each do | start_song |
聽聽聽聽聽聽start = start_song.first_letter
聽聽聽聽聽聽endings.each do | end_song |
聽聽ending = end_song.last_letter
聽聽if end_song.first_letter == start_song.last_letter
聽聽聽聽yield MonkeySonglist.new([start_song, end_song])
聽聽end
聽聽find_any(start, ending, max_depth - 1, used | [start_song,
end_song]) do | connection |
聽聽聽聽yield connection.add_left_right(start_song, end_song)
聽聽end
聽聽聽聽聽聽end
聽聽聽聽end
聽聽聽聽return nil
聽聽end

聽聽def find_best_matching_(from, to, match_evaluator, max_depth = -1,
used = Set.new)
聽聽聽聽return nil unless match_evaluator.continue?(used)
聽聽聽聽starts = (@starting_with[from] || Set.new) - used
聽聽聽聽endings = (@ending_with[to] || Set.new) - used
聽聽聽聽return nil if starts.empty? or endings.empty?
聽聽聽聽connections = starts & endings
聽聽聽聽if !connections.empty? # Found connection
聽聽聽聽聽聽connections.each do |s|
聽聽yield MonkeySonglist.new([s])
聽聽聽聽聽聽end
聽聽聽聽end
聽聽聽聽starts.each do | start_song |
聽聽聽聽聽聽start = start_song.first_letter
聽聽聽聽聽聽endings.each do | end_song |
聽聽ending = end_song.last_letter
聽聽if end_song.first_letter == start_song.last_letter
聽聽聽聽yield MonkeySonglist.new([start_song, end_song])
聽聽end
聽聽find_best_matching_(start, ending, match_evaluator, max_depth - 1,
used | [start_song, end_song]) do | connection |
聽聽聽聽yield connection.add_left_right(start_song, end_song)
聽聽end
聽聽聽聽聽聽end
聽聽聽聽end
聽聽聽聽return nil
聽聽end

聽聽def find_best_matching(from, to, match_evaluator, max_depth = -1)
聽聽聽聽find_best_matching_(from, to, match_evaluator, max_depth) do | connection |
聽聽聽聽聽聽match_evaluator.add_result(connection)
聽聽聽聽end
聽聽聽聽match_evaluator.best_match
聽聽end

聽聽class BasicMatchEvaluator
聽聽聽聽attr_reader :best_match, :best_delta
聽聽聽聽
聽聽聽聽def add_result(match)
聽聽聽聽聽聽delta = evaluate(match)
聽聽聽聽聽聽if !@best_delta || (delta < @best_delta)
聽聽@best_delta = delta
聽聽@best_match = match
聽聽$stderr.puts "Best so far: #{@best_delta}"
聽聽聽聽聽聽end
聽聽聽聽聽聽@best_match
聽聽聽聽end
聽聽end
聽聽
聽聽# Example for an evaluator. Different evaluators can be programmed
to implement any kind of minimization
聽聽class PlaytimeEvaluator < BasicMatchEvaluator
聽聽聽聽attr_reader :best_match, :best_delta
聽聽聽聽
聽聽聽聽def initialize(target_time)
聽聽聽聽聽聽@target_time = target_time
聽聽聽聽end
聽聽聽聽
聽聽聽聽def continue?(used)
聽聽聽聽聽聽if @best_delta
聽聽used_time = (used.inject(0) { | r, s | r + s.duration }
聽聽聽聽聽聽聽聽if used_time < @target_time
聽聽聽聽true
聽聽else
聽聽聽聽delta = (used_time - @target_time) ** 2
聽聽聽聽delta < @best_delta
聽聽end
聽聽聽聽聽聽else
聽聽true
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def evaluate(match)
聽聽聽聽聽聽(match.inject(0) { | r, s | r + s.duration } - @target_time) ** 2
聽聽聽聽end
聽聽end

聽聽def find_best_timefill(from, to, time)
聽聽聽聽evaluator = PlaytimeEvaluator.new(time)
聽聽聽聽find_best_matching(from, to, evaluator)
聽聽end
聽聽
聽聽def find_shortest(from, to)
聽聽聽聽1.upto(@song_count) do | max_depth |
聽聽聽聽聽聽$stdout.flush
聽聽聽聽聽聽find_any(from, to, max_depth) do | connection |
聽聽return connection
聽聽聽聽聽聽end
聽聽聽聽end
聽聽end
end

puts "Loading songlist"
monkeysongs = MonkeySongs.new

puts "Shortest monkeysonglist for c -> u"
puts monkeysongs.find_shortest('c', 'u').to_s
$stdout.flush
puts

puts "Shortest monkeysonglist for f -> y"
puts monkeysongs.find_shortest('f', 'y').to_s
$stdout.flush
puts

puts "Shortest monkeysonglist for a -> z"
puts monkeysongs.find_shortest('a', 'z').to_s
$stdout.flush
puts

puts "Find best timefill for a -> z 30min"
puts monkeysongs.find_best_timefill('a', 'z', 30 * 60 * 1000).to_s
$stdout.flush
puts
puts "All connections for f -> y"
monkeysongs.find_any('f', 'y') do | connection |
聽聽puts connection.to_s, "---"
聽聽$stdout.flush
end

路路路

--
http://ruby.brian-schroeder.de/

multilingual _non rails_ ruby based vocabulary trainer:
http://www.vocabulaire.org/ | http://www.gloser.org/ | http://www.vokabeln.net/

After removing the typos that somehow crept in there and adding a
ending condition for the fill_time search it now works. It finds a
playlist to fit into 30m +/- 5s of time and starting with a ending
with z in a little more than two minutes.

Alarm Call - Bj枚rk 4.33m
Afternoon In The Desert - Tangerine Dream 3.32m
A Different Drum - Peter Gabriel 4.67m
Asian tropical rain forest - SOUND EFFECTS 0.85m
Acceptance - Gabriel Yared 1.02m
Ersatz - Fischerspooner 3.93m
Waltz - Gabriel Yared 1.97m
Santa Cruz - David Qualey 2.18m
Tremolo Blooz - The Presidents of the United States of America 2.88m
Channel Z - The B-52's 4.84m

路路路

On 03/05/05, Brian Schr枚der <ruby.brian@gmail.com> wrote:

Hello Group,

I once again had the pleasure of solving a ruby quiz. Here's my solution.

I implemeted a bidirectional search and a mechanism to devise more
general search evaluation functions. As an example I implemented the
search for the barrel of monkeys that best fills up a given total
duration.

I tried to make it work with unicode strings, but I'm not so shure
regexps are the way to go here :wink:

Because I'm a bit short on time (and shouldn't even have made this
quiz), I have not even implemented an user interface.

have fun,

Brian

Find everything nicely wrapped up at:
http://ruby.brian-schroeder.de/quiz/monkeysongs/

                                                                      ------
                                                                      30.00m
took 141.35s

Thanks for the quiz,

Brian

--
http://ruby.brian-schroeder.de/

multilingual _non rails_ ruby based vocabulary trainer:
http://www.vocabulaire.org/ | http://www.gloser.org/ | http://www.vokabeln.net/

Nice fit, but it doesn't appear to be a Barrel of Monkeys playlist.

(AL -> AT -> AM -> AT -> AE -> EZ -> WZ -> SZ -> TZ -> CZ)

路路路

On May 6, 2005, at 3:48 AM, Brian Schr枚der wrote:

Alarm Call - Bj枚rk 4.33m
Afternoon In The Desert - Tangerine Dream 3.32m
A Different Drum - Peter Gabriel 4.67m
Asian tropical rain forest - SOUND EFFECTS 0.85m
Acceptance - Gabriel Yared 1.02m
Ersatz - Fischerspooner 3.93m
Waltz - Gabriel Yared 1.97m
Santa Cruz - David Qualey 2.18m
Tremolo Blooz - The Presidents of the United States of America 2.88m
Channel Z - The B-52's 4.84m
                                                                      ------
                                                                      30.00m
took 141.35s

One should not work on two things at the same time. Nicely spotted ;). *blush*

regards,

Brian

路路路

On 06/05/05, Gavin Kistner <gavin@refinery.com> wrote:

On May 6, 2005, at 3:48 AM, Brian Schr枚der wrote:
> Alarm Call -
> Bj枚rk 4.33m
> Afternoon In The Desert - Tangerine
> Dream 3.32m
> A Different Drum - Peter
> Gabriel 4.67m
> Asian tropical rain forest - SOUND
> EFFECTS 0.85m
> Acceptance - Gabriel
> Yared 1.02m
> Ersatz -
> Fischerspooner 3.93m
> Waltz - Gabriel
> Yared 1.97m
> Santa Cruz - David
> Qualey 2.18m
> Tremolo Blooz - The Presidents of the United States of
> America 2.88m
> Channel Z - The
> B-52's 4.84m
>
> ------
>
> 30.00m
> took 141.35s

Nice fit, but it doesn't appear to be a Barrel of Monkeys playlist.

(AL -> AT -> AM -> AT -> AE -> EZ -> WZ -> SZ -> TZ -> CZ)

--
http://ruby.brian-schroeder.de/

multilingual _non rails_ ruby based vocabulary trainer:
http://www.vocabulaire.org/ | http://www.gloser.org/ | http://www.vokabeln.net/