#!/usr/bin/env ruby
#
# Solution to RubyQuiz 105: Tournament Matchups
# Lou Scoras <louis.j.scoras / gmail.com>
#
# This is a pretty simple solution using a binary tree to represent the
# brackets. Binary trees are a natural way to represent them since two
# teams play one another per in each game. We just need to make sure that
# we keep the trees balanced -- in this case, not because of performance but
# for correctness: i.e. teams shouldn't get more than one 'bye'.
##
# The Bracket class is where all the action takes place. We'll just keep
# adding teams into the Bracket in order of their ranking. By swapping the
# left and right branches after inserts, we can assure that the next team is
# always inserted into the correct position.
class Bracket
##
# The Bracket constructor is trivial in most cases. Since we won't be
# defining a separate Leaf class, we preform a little trickery until there
# are at least two teams added.
def initialize(left=nil,right=nil)
@left = left
@right = right
##
# Count the non-nil entries, if both are non-nil we just set the count
# using the regular method of adding the sub children counts.
# Otherwise, we use the total of non-nil arguments:
c = [@left,@right].inject(0) {|c,i| i.nil?? 0 : 1}
if c < 2
@count = c + 1
else
@count = left.count + right.count + 1
end
end
##
# Insert a team into the bracket. Again this method has special case
# handling for when we don't yet have two teams entered.
#
# Assuming there are two children nodes, we start off by comparing the
# number of elements in each. The non-equal cases are standard fare,
# except that we swap the left and right trees afterward. The reason for
# that being that in the equal case, we favor the right tree. The
# swapping makes sure that new team gets entered into the tree with more
# talent. Since the best teams are generally paired up with the worst, it
# also ensures that lower ranked teams get extra games while the upper
# teams retain the byes.
def insert(team)
@left = leafify(team) and return if @left.nil?
@right = leafify(team) and return if @right.nil?
case @left.count <=> @right.count
when 1
do_insert(:@right, team)
swap!
when -1
do_insert(:@left, team)
swap!
when 0
do_insert(:@right, team)
end
@count += 1
end
##
# Just a helper to switch the two sub-trees.
def swap!
@left, @right = @right, @left
end
##
# do_insert is a helper for performing the inserts on the subtrees. The
# only reason it's needed is because there isn't an explicit leaf class.
# We'll check to see if it's a leaf and if it is, create a new node
# combining the new team with the single leaf node. Notice how the right
# subtree is still favored.
def do_insert(thing, team)
target = instance_variable_get(thing)
if target.leaf?
instance_variable_set(thing,
self.class.new(target, leafify(team))
)
else
target.insert(team)
end
end
##
# We need some way to view the matchups.
def to_s
"[#@left vs. #@right]"
end
private :do_insert
attr_reader :count
##
# None of our class should be a leaf. We'll handle the polymorphism by
# mucking around with the team parameters passed in.
def leaf?; false end
##
# To get a leaf node, we just mixin two trivial functions for whatever
# class is chosen to represent the teams. This is probably just sloppy OO
# design, but it sure is convienient.
def leafify(n)
n.extend(Leaf)
end
end
##
# These are the functions mixed into the team class.
module Leaf
def count; 1 end
def leaf?; true end
end
##
# Just for some flavor we'll add some team names. They aren't really in any
# particular order -- except for Ruby =) And maybe Haskell...
Teams = %w{ Rubies Haskells Lisps Perls
Schemes Korns OCamls Pythons
Javas Cs Basics PHPs JavaScripts
SASes Bashes Erlangs SQLs Logos
Fortrans Awks Luas Smalltalks }
def team(i)
str = Teams[i-1] ? (" " + Teams[i-1]) : ""
end
##
# The main program is boring. Just get the number of teams from the command
# line and build the bracket. Notice how we're using a string to represent
# the teams. This is fine and the bracket just mixes in the sential
# functions. One drawback to this is that you can't use a class that is
# represented by an intermediate value. Try it with a Fixnum and you'll see
# what I mean.
bracket = Bracket.new
(1..Integer(ARGV[0])).each do |i|
bracket.insert(i.to_s + team(i)) # Can't extend Fixnum
end
##
# Print the bracket using the to_s incredibly simple to_s method above.
puts bracket