[QUIZ] Tournament Matchups (#105)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Demetrius Nunes

In a single-elimination tournament, there is usually a previously established
ranking for the participating players or teams, such as that the best players or
teams are matched against the worst ones. This is done this way so there is a
higher chance for the top players/teams to meet in the final.

For example, in a small 8-player tournament, there would be 3 rounds. This first
round would be setup like this:

Round 1
1 x 8
2 x 7
3 x 6
4 x 5

This is easy enough. The tough part is arranging the pairing for the following
rounds respecting the best vs worst rule, so, imagining that all the favorites
won their games, we would have 1x4 and 2x3 in round 2 and then finally 1x2 in
the final. For this to happen, the tournament would have to be arranged this
way:

R1 R2 R3
============
1
---
|---
--- |
8 |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
7 | |
|---
3 |
--- |
|---
---
6

If the numbers of players/teams is not a potency of 2, then the top players
would have a "bye" in the first round, so, a 6-player draw would go like this:

R1 R2 R3
============
1
---
|---
--- |
bye |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
bye | |
|---
3 |
--- |
|---
---
6

So, this quiz is about writing a single-elimination tournament generator for any
number of players/teams obeying the best vs worst rule in all rounds.

For a quick look at correct answers see this "got-the-job-done" javascript/html
implementation at:

http://www.crowsdarts.com/brackets/playoff-chart.html

We are looking for correct data modeling and calculation, not for correct
presentation output of the tournament draw, but it would be nice to implement a
#to_s output like the ones seen above (or even better: how about a beautiful
RMagick generated image?!). In all cases, keep the model and presentation
cleanly separated.
 
D

Daniel Finnie

I have some questions about this problem.

First http://www.crowsdarts.com/brackets/playoff-chart.html, for 5
teams, is the output correct? It gives the following match-ups for the
1st round:
1 vs. bye
4 vs. 5
2 vs. 3

Wouldn't the correct answer be:
1 vs. bye
5 vs. 2
4 vs. 3

Also, are there allowed to be any "byes" in the 2nd or greater rounds?

Thanks,
Dan

Ruby said:
The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Demetrius Nunes

In a single-elimination tournament, there is usually a previously established
ranking for the participating players or teams, such as that the best players or
teams are matched against the worst ones. This is done this way so there is a
higher chance for the top players/teams to meet in the final.

For example, in a small 8-player tournament, there would be 3 rounds. This first
round would be setup like this:

Round 1
1 x 8
2 x 7
3 x 6
4 x 5

This is easy enough. The tough part is arranging the pairing for the following
rounds respecting the best vs worst rule, so, imagining that all the favorites
won their games, we would have 1x4 and 2x3 in round 2 and then finally 1x2 in
the final. For this to happen, the tournament would have to be arranged this
way:

R1 R2 R3
============
1
---
|---
--- |
8 |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
7 | |
|---
3 |
--- |
|---
---
6

If the numbers of players/teams is not a potency of 2, then the top players
would have a "bye" in the first round, so, a 6-player draw would go like this:

R1 R2 R3
============
1
---
|---
--- |
bye |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
bye | |
|---
3 |
--- |
|---
---
6

So, this quiz is about writing a single-elimination tournament generator for any
number of players/teams obeying the best vs worst rule in all rounds.

For a quick look at correct answers see this "got-the-job-done" javascript/html
implementation at:

http://www.crowsdarts.com/brackets/playoff-chart.html

We are looking for correct data modeling and calculation, not for correct
presentation output of the tournament draw, but it would be nice to implement a
#to_s output like the ones seen above (or even better: how about a beautiful
RMagick generated image?!). In all cases, keep the model and presentation
cleanly separated.
 
J

James Edward Gray II

I have some questions about this problem.

First http://www.crowsdarts.com/brackets/playoff-chart.html, for 5
teams, is the output correct? It gives the following match-ups for
the 1st round:
1 vs. bye
4 vs. 5
2 vs. 3

Wouldn't the correct answer be:
1 vs. bye
5 vs. 2
4 vs. 3

Your way seems more correct to me, yes.
Also, are there allowed to be any "byes" in the 2nd or greater rounds?

I haven't tried the problem myself yet, but I'm pretty sure you only
need them in the first round, if you work out the ordering
correctly. Someone correct me if I'm wrong on that though...

James Edward Gray II
 
L

Louis J Scoras

#!/usr/bin/env ruby
#
# Solution to RubyQuiz 105: Tournament Matchups
# Lou Scoras <[email protected]>
#
# 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
 
L

Louis J Scoras

I have some questions about this problem.

First http://www.crowsdarts.com/brackets/playoff-chart.html, for 5
teams, is the output correct? It gives the following match-ups for the
1st round:
1 vs. bye
4 vs. 5
2 vs. 3

I think you're misreading this. It says that all teams have a 'bye'
in the first round excepting teams 4 and 5. You could argue that the
correct answer would have the least amount of 'bye's in it, but the
quiz I don't think mentions that.
 
D

Daniel Finnie

I assumed the extended bracket for 2 and 3 was done on purpose by the
program to have the output look nicer.

1 vs. bye
4 vs. 5
2 vs. 3
This has 4 games, a power of 2.

1 vs. bye
2 vs. bye
3 vs. bye
4 vs. 5
This has 2 games, a power of 2, but doing it this way seems stupid,
especially when for other problems it seems to give solutions with the
least amount of byes, or the highest power of 2 possible games.

Basically, either way you interpret the online version, I think
something's wrong.

Dan
 
B

Brock Lee

The goal is not to minimize the number of byes. The goal is to
maximize fairness and competetiveness.

In the last round, when it's down to two teams, the greater the
discrepency in the number of games those two teams have played in the
tournment the less fair it is and the less competetive it is.
Therefore those final two teams should have either played the same
number of games or differ by at most one game played.

If byes can occur after the first round, then that stipulation will not
necessarily be true.

Therefore, byes can only occur in the first round. By the time it gets
to the second round, the number of teams who are still alive in the
tournament should be a power of two.

Thus this interpretation is correct:
* 1 vs. bye
* 2 vs. bye
* 3 vs. bye
* 4 vs. 5

And that's what http://www.crowsdarts.com/brackets/playoff-chart.html
produces.

Brock Lee
 
F

Fred Wulff

I wanted to see how simply I could do it from a code perspective, so I
just used nested arrays to represent the tree structure. I think it
actually turned out pretty well. Turning it into an actual tree
results in pretty similar code, removing the niceness of zip, but
making the recursive functions a little OOPier.

-Fred

# Generate a single elimination tournament for N teams, numbered 1..N
def generate_tournament_bracket(num_teams)
# Number of byes = 2**n - num_teams where n is the smallest value
such the number is positive
num_byes = 2**((Math.log(num_teams)/Math.log(2)).ceil) - num_teams

# Generate bye first round "matching"s
byes = (1..num_byes).map{|i| }

# Generate all the other matchings
current_round = ((num_byes + 1)..num_teams).to_a

# Keep going until we have a winner
while current_round.size > 1
# Generate the next round matchings by taking all byes, if any,
from the byes array.
# Then we exploit the fact that our array is always sorted by seed
of winner to find the next
# pairings
current_round = byes.slice!(0..-1) +
current_round[0...current_round.size/2].zip(current_round[current_round.size/2..-1].reverse)
end
return current_round[0]
end


# Outputs any matches in the subtree of current_matching using
match_num as the first
# available match_number
# Returns the match number of current_matching
def output_bracket(current_matching, match_num = 1)
# Since we never go until we get current_matching.kind_of?(Fixnum),
we must have a bye
# which means we're first round
if current_matching.size == 1
puts "Match #{match_num}: #{current_matching[0]} vs Bye"
return match_num

# First round detection
elsif current_matching[0].kind_of?(Fixnum)
puts "Match #{match_num}: #{current_matching[0]} vs #{current_matching[1]}"
return match_num

# Some other round, so we need to recurse down
else
left_match = output_bracket(current_matching[0], match_num)
right_match = output_bracket(current_matching[1], left_match + 1)
match_num = right_match + 1

puts "Match #{match_num}: Winner of Match #{left_match} vs Winner
of Match #{right_match}"

return match_num
end
end

output_bracket(generate_tournament_bracket(5))
 
D

Dema

Here is my original solution and a sample run for 10 players.

I decided to follow this simple approach: for the first round, make a
normalized list of all players (by normalized I mean it must be a
potency of 2 - if not, the last slots should be filled with nils until
it is) and pair them picking from the head and the tail of the list.
After that, make a list from the matches (not the players) of the 1st
round and repeat until there are less than 2 matches.

# draw.rb
class Match

# All matches have an Id, a top player/team and bottom player/team
attr_reader :id, :top, :bottom

def initialize(id, top, bottom)
@id = id
@top = top
@bottom = bottom
end
end

# a Draw is composed of rounds and matches
class Draw
attr_reader :rounds
attr_reader :matches

# here is the generation of the match-ups
def initialize(players)
@matches = [] # the list of all matches
@rounds = {} # the hash of matches for each round

match = round = 1

# normalize players count into square potency
nsqrplayers = 2 ** Math.frexp(players.size - 1).last

# derive candidates for 1st round, setting byes for top players
candidates = (1..nsqrplayers).to_a.map { |c| c > players.size ? nil
: players[c-1] }

while (ncandidates = candidates.size) >= 2
while !candidates.empty?
@rounds[round] ||= []

# setup first x last matches from the candidates list
@rounds[round] << @matches[match] = Match.new(match,
candidates.shift,

candidates.pop)
match += 1
end

# derive candidates for remaining rounds, but now
# the candidates will appear in the form of match Ids
# so let's map the candidates from the winners of the previous
matches
candidates =
(((@rounds[round].first.id)..(@rounds[round].last.id))).to_a.map do |m|

# was it a bye?
@matches[m].bottom.nil? ? "#{@matches[m].top}" : "W#{m}"
end
round += 1
end
end

def to_s
buf = ""
for r in @rounds.keys.sort
buf << "R#{r}\n"
for m in @rounds[r]
buf << "M#{m.id}: #{m.top} x #{m.bottom.nil? ? 'bye' :
m.bottom}\n"
end
buf << "\n"
end
buf
end
end

players = (1..10).to_a
puts Draw.new(players).to_s

Here's the sample output:
R1
M1: 1 x bye
M2: 2 x bye
M3: 3 x bye
M4: 4 x bye
M5: 5 x bye
M6: 6 x bye
M7: 7 x 10
M8: 8 x 9

R2
M9: 1 x W8
M10: 2 x W7
M11: 3 x 6
M12: 4 x 5

R3
M13: W9 x W12
M14: W10 x W11

R4
M15: W13 x W14


Ruby said:
The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Demetrius Nunes

In a single-elimination tournament, there is usually a previously established
ranking for the participating players or teams, such as that the best players or
teams are matched against the worst ones. This is done this way so there is a
higher chance for the top players/teams to meet in the final.

For example, in a small 8-player tournament, there would be 3 rounds. This first
round would be setup like this:

Round 1
1 x 8
2 x 7
3 x 6
4 x 5

This is easy enough. The tough part is arranging the pairing for the following
rounds respecting the best vs worst rule, so, imagining that all the favorites
won their games, we would have 1x4 and 2x3 in round 2 and then finally 1x2 in
the final. For this to happen, the tournament would have to be arranged this
way:

R1 R2 R3
============
1
---
|---
--- |
8 |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
7 | |
|---
3 |
--- |
|---
---
6

If the numbers of players/teams is not a potency of 2, then the top players
would have a "bye" in the first round, so, a 6-player draw would go like this:

R1 R2 R3
============
1
---
|---
--- |
bye |
|---
4 | |
--- | |
|--- |
--- |
5 |
|----
2 |
--- |
|--- |
--- | |
bye | |
|---
3 |
--- |
|---
---
6

So, this quiz is about writing a single-elimination tournament generator for any
number of players/teams obeying the best vs worst rule in all rounds.

For a quick look at correct answers see this "got-the-job-done" javascript/html
implementation at:

http://www.crowsdarts.com/brackets/playoff-chart.html

We are looking for correct data modeling and calculation, not for correct
presentation output of the tournament draw, but it would be nice to implement a
#to_s output like the ones seen above (or even better: how about a beautiful
RMagick generated image?!). In all cases, keep the model and presentation
cleanly separated.
 
R

rubytraining

Below you'll find my solution. The code to create the binary tree is
pretty small. The code to produce the text version of the tournament
tree is not so small, however. Oh well...

Eric


SAMPLE OTUPUT

$ ruby tournament.rb 5
R1 R2 R3
============
1
---+
+---+
---+ |
bye |
+---+
4 | |
---+ | |
+---+ |
---+ |
5 |
+---
2 |
---+ |
+---+ |
---+ | |
bye | |
+---+
3 |
---+ |
+---+
---+
bye



SOLUTION

# Solution for Ruby Quiz #105
# Author: Eric I.
# December 10, 2006
# www.learnruby.com

class Numeric
# A monkey-patched convenience method to compute the maximum of two
# numbers.
def max(other)
if self >= other : self
else other
end
end
end


class Integer
# A monkey-patched method to compute the gray code of an integer.
The
# gray code has properties that make it helpful to the tournament
problem.
def gray_code
self ^ (self >> 1)
end
end


# A tournament is really a node in a binary tree. The value in each
# node contains the ranking of the best ranking team contained in the
# tournament tree below.
class Tournament
attr_reader :ranking

def initialize(ranking)
@ranking = ranking
end

# Creates a tournament with the given number of teams.
def self.create(teams)
# create the initial node
head_node = Tournament.new(1)

# insert additional nodes for each further team
for ranking in 2..teams
head_node.add_team(ranking)
end

head_node
end

# Adds a team with the given ranking to the tournament. It turns out
# that the gray code of the ranking-1 has a bit pattern that
conveniently
# helps us descend the binary tree to the appropriate place at which
to
# put the team.
def add_team(ranking)
add_team_help(ranking, (ranking - 1).gray_code)
end

# Returns the number of rounds in the tournament. This is determined
by
# taking the max of the depths of the two sub-trees and adding one.
def rounds
unless @left : 0
else 1 + (@left.rounds.max(@right.rounds))
end
end

# Returns the pairs playing at a given round. A round number of 1 is
# the first round played and therefore the bottom-most layer of the
tree.
def round(level)
round_help(rounds - level)
end

# Converts the tournament tree into a String representation.
def to_s
lines = [] # store the result as an array of lines initially

# create the lowest layer of the tree representing the first round
round(1).each do |game|
lines << game[0].to_s.rjust(3)
lines << "---"
lines << " "
lines << "---"
lines << game[1].to_s.rjust(3)
lines << " "
end
lines.pop # last line, which just contains blanks, is not needed

# the rest of the text tree is made through textual operations
# by connecting teams playing with veritcal lines, then branching
# horizontally to the next level, and then extending those branches
begin
count = to_s_connect(lines)
to_s_branch(lines)
3.times { to_s_extend(lines) }
end until count == 1

header_string + lines.join("\n")
end


protected

# Recursively descends the tree to place a team with a new ranking.
# Ultimately it will create two new nodes and insert them into the
# tree representing itself and the team to be played. When
# descending the three, the bits in the gray code of the ranking
# from least-significant to most-significant indicate which branch
# to take.
def add_team_help(ranking, gray_code)
if @left == nil
# bottomed out; create two new nodes
@left = Tournament.new(@ranking)
@right = Tournament.new(ranking)
elsif gray_code % 2 == 0
# bit in gray code indicates the left branch
@left.add_team_help(ranking, gray_code >> 1)
else
# bit in gray code indicates the right branch
@right.add_team_help(ranking, gray_code >> 1)
end
end

# Returns the teams playing at the given round level. The parameter
# is actually the desired round subtracted from the number of
# rounds. That way we know we're at the right level when it reaches
# zero. It can be the case where a given branch does not have
# enough levels; that indicates a "bye" for a good-ranking team.
def round_help(reverse_level)
if @left == nil : [[@ranking, "bye"]]
elsif reverse_level == 0 : [[@left.ranking, @right.ranking]]
else @left.round_help(reverse_level - 1) +
@right.round_help(reverse_level - 1)
end
end

# Creates a simple pair of lines showing the round numbers; this
helps
# in the interpretation of the text-tree below.
def header_string
result = (1..rounds).to_a.inject("") do |collect, round|
collect + "R#{round}".center(4)
end
result + "\n" + "=" * result.length + "\n"
end

# Creates vertical lines used to indicate a game and that connect
# the horizontal lines that refer to teams. The teams referred to
# are either from the first round or that have won the previous
# round.
def to_s_connect(lines)
count = 0
connect = false
lines.each do |line|
if line[-1, 1] == "-"
line << "+"
connect = !connect
count += 1 if connect
elsif connect
line << "|"
else
line << " "
end
end
count
end

# From the vertical lines used to represent a game, this places a
# horizontal line in the *middle* of it which indicates the winning
# team. Except for the final round, these horizontal lines will be
# used to create a game at the next round.
def to_s_branch(lines)
range_began = false
lines.each_index do |i|
if lines[-1, 1] == "|"
range_began = i unless range_began
elsif range_began
lines[(i + range_began - 1)/2][-1] = "+"
range_began = false
end
#lines << " "
end
end

# Extends the horizontal lines by one character.
def to_s_extend(lines)
lines.each do |line|
if line =~ /(-| \+)$/
line << "-"
else
line << " "
end
end
end
end


if ARGV.length != 1
$stderr.puts "Usage: #{$0} team-count"
exit 1
end

tournament = Tournament.create(ARGV[0].to_i)

puts tournament
 
M

Max Muermann

Here's my solution. It assumes that the teams are ranked by skill. I
wanted to see whether this could be done in-place and am doing some
recursive swapping to arrive at the correct solution.

There's an additional pass through the array at the end to remove
cases where two teams have received byes for the first round and are
scheduled to play each other in the second round.
No pretty printing - sorry. Byes are nil in the resulting array of pairings.

Here are some results:

"8 players:"
[[1, 8], [4, 5], [2, 7], [3, 6]]

"6 players:"
[[1, nil], [4, 5], [2, nil], [3, 6]]

"16 players:"
[[1, 16], [8, 9], [4, 13], [5, 12], [2, 15], [7, 10], [3, 14], [6, 11]]

"11 players:"
[[1, nil], [8, 9], [4, 5], [2, nil], [7, 10], [3, nil], [6, 11]]

"32 players:"
[[1, 32], [16, 17], [8, 25], [9, 24], [4, 29], [13, 20], [5, 28], [12,
21], [2, 31], [15, 18], [7, 26], [10, 23], [3, 30], [14, 19], [6, 27],
[11, 22]]

Cheers,
Max


require 'enumerator'

# swap the second quarter of an array with the last quarter
def swap_interleaved(a)
n = a.size >> 2
a[n..2*n-1],a[3*n..4*n-1] = a[3*n..4*n-1],a[n..2*n-1]
end

# swap the third quarter of an array with the last quarter
def swap(a)
n = a.size >> 2
a[2*n..3*n-1],a[3*n..4*n-1] = a[3*n..4*n-1],a[2*n..3*n-1]
end

# for the given array, swap_interleaved, then swap
# if level is not reached, split array in half and recurse for both halves
def rec(a, level)
swap_interleaved a
swap(a)
if (level>0)
a[0..a.size/2-1] = rec(a[0..a.size/2-1], level-1)
a[a.size/2..-1] = rec(a[a.size/2..-1], level-1)
end
a
end

def match_up(num_players)
# match up first-round pairings
n = (Math.log(num_players-1)/Math.log(2)).to_i+1

# new array (2**n in size)
a = Array.new(2**n)

# add players
a[0..num_players-1] = (1..num_players).to_a

# make first-round pairings
a = a[0..a.size/2-1].zip(a[a.size/2..-1].reverse)

# recurse
(n-3).downto(0) do |l|
rec(a,l)
end

# remove double byes
result = []
a.each_slice(2) do |a,b|
if a[1] || b[1]
result << a << b
else
result << [a[0],b[0]]
end
end
result
end

p "8 players:"
p match_up(8)

p "6 players:"
p match_up(6)

p "16 players:"
p match_up(16)

p "11 players:"
p match_up(11)

p "32 players:"
p match_up(32)
 
M

Matthew Moss

Well, unfortunately, I don't have time to generate the chart, but it
was easy enough to generate the pairings... This could probably be
easily golfed, but I figured just leave it be.

The output is "stacked" array pairs... What I mean by that is that the
top array has two items: left and right. Each of those items is an
array of two items: left and right.... etc until you get down to the
leaves. A pair like [1, 2] means "Team 1 vs Team 2", while a pair
like [6, nil] means "Team 6 gets a bye."


class Integer
def ceilPow2
n = self - 1
i = 1
until (n >> i).zero?
n |= (n >> i)
i *= 2
end
n += 1
end

def even?
(self % 2).zero?
end
end

class Array
def fold
raise ArgumentError unless size.even?
h = size / 2
self[0,h].zip(self[h,h].reverse)
end
end

def matchup(n)
raise ArgumentError unless n > 0

byes = n.ceilPow2 - n
mups = (1..n).to_a + [nil] * byes

until mups.size == 1
mups = mups.fold
end

mups[0]
end

def report(mups)
# Here is where you could do tree output or similar... but I've no time.
p mups
end

numTeams = (ARGV[0] || 23).to_i
if numTeams < 2
puts "C'mon, that's not much of a competition..."
else
matchUps = matchup(numTeams)
report(matchUps)
end
 
W

William James

Ruby said:
by Demetrius Nunes

In a single-elimination tournament, there is usually a previously established
ranking for the participating players or teams, such as that the best players or
teams are matched against the worst ones. This is done this way so there is a
higher chance for the top players/teams to meet in the final.

For example, in a small 8-player tournament, there would be 3 rounds. This first
round would be setup like this:

Round 1
1 x 8
2 x 7
3 x 6
4 x 5


Inf = 999

def find_best x
Array( x ).flatten.min
end

class Array
def partition_teams
t = sort_by{|x| find_best( x ) }
[ t[0,t.size/2], t[t.size/2..-1].reverse ]
end
end

num_teams = ARGV.shift.to_i

n = 1
begin n *= 2 end until n >= num_teams
teams = (1..num_teams).to_a + [Inf] * (n - num_teams)

while teams.size > 2 do
teams = teams.partition_teams.transpose
end
f = nil
p teams.flatten.partition{f=!f}.transpose
 
W

William James

William said:
Ruby said:
by Demetrius Nunes

In a single-elimination tournament, there is usually a previously established
ranking for the participating players or teams, such as that the best players or
teams are matched against the worst ones. This is done this way so there is a
higher chance for the top players/teams to meet in the final.

For example, in a small 8-player tournament, there would be 3 rounds. This first
round would be setup like this:

Round 1
1 x 8
2 x 7
3 x 6
4 x 5


Inf = 999

def find_best x
Array( x ).flatten.min
end

class Array
def partition_teams
t = sort_by{|x| find_best( x ) }
[ t[0,t.size/2], t[t.size/2..-1].reverse ]
end
end

num_teams = ARGV.shift.to_i

n = 1
begin n *= 2 end until n >= num_teams
teams = (1..num_teams).to_a + [Inf] * (n - num_teams)

while teams.size > 2 do
teams = teams.partition_teams.transpose
end
f = nil
p teams.flatten.partition{f=!f}.transpose

Simpler:

num_teams = ARGV.shift.to_i

n = 1 ; begin n *= 2 end until n >= num_teams
teams = (1..num_teams).to_a + ["bye"] * (n - num_teams)

while (n = teams.size) > 2 do
teams = teams[0, n/2].zip( teams[n/2 .. -1].reverse )
end
p teams.flatten.partition{n=!n}.reverse.transpose


"The most valuable of all talents is that of never using two words
when one will do. "
-Thomas Jefferson

"Programmers are always surrounded by complexity; we cannot avoid
it.... If our basic tool, the language in which we design and code
our programs, is also complicated, the language itself becomes part
of the problem rather than part of its solution. "
-C. A. R. Hoare (1980 Turing Award Lecture)

"To attain knowledge, add things every day.
To attain wisdom, remove things every day."
-Lao-tse

"Perfection is attained, not when there is nothing left to add, but
when there is nothing left to take away. "
-Antoine de Saint-Exupery
 
J

James Edward Gray II

Simpler:

num_teams = ARGV.shift.to_i

n = 1 ; begin n *= 2 end until n >= num_teams
teams = (1..num_teams).to_a + ["bye"] * (n - num_teams)

while (n = teams.size) > 2 do
teams = teams[0, n/2].zip( teams[n/2 .. -1].reverse )
end
p teams.flatten.partition{n=!n}.reverse.transpose


"The most valuable of all talents is that of never using two words
when one will do. "
-Thomas Jefferson

"Programmers are always surrounded by complexity; we cannot avoid
it.... If our basic tool, the language in which we design and code
our programs, is also complicated, the language itself becomes part
of the problem rather than part of its solution. "
-C. A. R. Hoare (1980 Turing Award Lecture)

"To attain knowledge, add things every day.
To attain wisdom, remove things every day."
-Lao-tse

"Perfection is attained, not when there is nothing left to add, but
when there is nothing left to take away. "
-Antoine de Saint-Exupery

I don't mean to play netiquette police here, but there is about twice
the amount of clever quotes as there is content in this message.
Maybe that signature is a bit long.

James Edward Gray II
 
M

Matthew Moss

I don't mean to play netiquette police here, but there is about twice
the amount of clever quotes as there is content in this message.
Maybe that signature is a bit long.

*nod*, agreed. And I would say it's not even just netiquette...
Signal-to-noise ratio on that message was pretty low... I prefer it
when it's much higher.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top