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
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/