Permalink
Browse files

Adds PositionalRelation for a new method of finding Nodes

Additionally adds depth to Nodes, total depth of bracket and total depth of each
side to the Bracket.
  • Loading branch information...
1 parent ebac437 commit 5728ae9fe631d6e57c3b6c198c044ee419325667 @cadwallion cadwallion committed Aug 18, 2012
View
@@ -1,4 +1,5 @@
require 'json'
+require 'bracket_tree/positional_relation'
require 'bracket_tree/match'
require 'bracket_tree/bracket'
require 'bracket_tree/template'
@@ -26,6 +26,8 @@ module Bracket
class Base
class NoTemplateError < Exception ; end
+ include PositionalRelationDelegators
+
class << self
def by_size size, options = {}
generate_from_template @template, size
@@ -55,11 +57,16 @@ def generate_from_template template, size
end
include Enumerable
- attr_accessor :root, :seed_order, :matches, :insertion_order
+ attr_accessor :root, :seed_order, :matches, :insertion_order, :depth
def initialize options = {}
@insertion_order = []
@matches = []
+ @depth = {
+ total: 0,
+ left: 0,
+ right: 0
+ }
if options[:matches]
options[:matches].each do |m|
@@ -81,22 +88,34 @@ def add position, data
if @root.nil?
@root = node
+ @depth[:total] = 1
+ @depth[:left] = 1
+ @depth[:right] = 1
else
current = @root
+ current_depth = 2
loop do
if node.position < current.position
if current.left.nil?
+ node.depth = current_depth
current.left = node
+
+ depth_check current_depth, node.position
break
else
current = current.left
+ current_depth += 1
end
elsif node.position > current.position
if current.right.nil?
+ node.depth = current_depth
current.right = node
+
+ depth_check current_depth, node.position
break
else
current = current.right
+ current_depth += 1
end
else
break
@@ -105,6 +124,12 @@ def add position, data
end
end
+ def depth_check depth, position
+ @depth[:total] = [depth, @depth[:total]].max
+ @depth[:left] = [depth, @depth[:left] ].max if position < @root.position
+ @depth[:right] = [depth, @depth[:right]].max if position > @root.position
+ end
+
# Replaces the data at a given node position with new payload. This is useful
# for updating bracket data, replacing placeholders with actual data, seeding,
# etc..
@@ -212,15 +237,20 @@ def match_loser seat
def in_order(node, block)
if node
- unless node.left.nil?
- in_order(node.left, block)
- end
+ in_order(node.left, block) unless node.left.nil?
block.call(node)
- unless node.right.nil?
- in_order(node.right, block)
- end
+ in_order(node.right, block) unless node.right.nil?
+ end
+ end
+
+ def top_down(node, &block)
+ if node
+ block.call(node)
+
+ top_down(node.left, &block) unless node.left.nil?
+ top_down(node.right, &block) unless node.right.nil?
end
end
end
View
@@ -1,6 +1,6 @@
module BracketTree
class Node
- attr_accessor :left, :right, :position, :payload
+ attr_accessor :left, :right, :position, :payload, :depth
def initialize position, payload = nil
@position = position
@@ -0,0 +1,162 @@
+require 'forwardable'
+
+module BracketTree
+ # PositionalRelation
+ #
+ # This class is a relational object used for constructing tree traversal when
+ # you do not know the exact position value that you are looking for. It uses
+ # two types of methods: relation condition methods and relation access methods.
+ #
+ # Relation Condition Methods
+ #
+ # Relation condition methods take the original `PositionalRelation` object and
+ # add conditions, returning the original object for easy chaining of relation
+ # conditions. Actual traversal does not happen with these methods.
+ #
+ # @example
+ # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
+ # relation = BracketTree::PositionalRelation.new(bracket) # => <BracketTree::PositionalRelation:0x007fa9431e1060>
+ # relation.winners # => <BracketTree::PositionalRelation:0x007fa9431e1060>
+ # relation.round(4) # => <BracketTree::PositionalRelation:0x007fa9431e1060>
+ #
+ # Relation conditions can be chained to create very specific or very broad
+ # options. Once you call a Relation Access Method, it will build based on the
+ # chain.
+ #
+ # @example Complex chaining of relations
+ # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
+ # relation = BracketTree::PositionalRelation.new(bracket)
+ # relation.winners.round(4).seat(3) # => <BracketTree::Node:0x01e37>
+ #
+ # Typically you will not directly instance BracketTree::RelationalPosition due
+ # to the need to pass in a bracket. Instead, bracket objects have access to these
+ # methods directly, and will return a BracketTree::PositionalRelation object.
+ #
+ # Relation Access Methods
+ #
+ # Relation access methods take the complete set of relation conditions and uses
+ # them as instructions for traversing the tree to collect the Node(s) requested.
+ #
+ # @example
+ # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
+ # bracket.winners.round(4).first # => <BracketTree::Node:0x007fa9271e73e0>
+ class PositionalRelation
+ attr_reader :bracket
+
+ def initialize bracket
+ @bracket = bracket
+ end
+
+ # Retrieves the first seat based on the stored relation conditions
+ #
+ # @return [BracketTree::Node, nil]
+ def first
+ seats = all
+ seats.first
+ end
+
+ # Retrieves the last seat based on the stored relation conditions
+ #
+ # @return [BracketTree::Node, nil]
+ def last
+ seats = all
+ seats.last
+ end
+
+ # Retrieves all seats based on the stored relation conditions
+ #
+ # @return [Array<BracketTree::Node>]
+ def all
+ if @side
+ if @round
+ seats = by_round @round, @side
+ else
+ side_root = @bracket.root.send(@side)
+ seats = []
+
+ @bracket.top_down(side_root) do |node|
+ seats << node
+ end
+ end
+ else
+ if @round
+ seats = by_round(@round, :left) + by_round(@round, :right)
+ else
+ seats = []
+ @bracket.top_down(@bracket.root) do |node|
+ seats << node
+ end
+ end
+ end
+
+ seats
+ end
+
+ # Retrieves the `number`-th seat from the given relation conditions
+ #
+ # @param [Fixnum] seat number
+ # @return [BracketTree::Node, nil] the seat requested
+ def seat number
+ seats = all
+
+ seats[number-1]
+ end
+
+ # Retrieves an Array of Nodes for a given round on a given side
+ #
+ # @param [Fixnum] round to pull
+ # @param [Fixnum] side of the tree to pull from
+ # @return [Array] array of Nodes from the round
+ def by_round round, side
+ depth = @bracket.depth[side] - (round - 1)
+ seats = []
+
+ side_root = @bracket.root.send(side)
+ @bracket.top_down(side_root) do |node|
+ if node.depth == depth
+ seats << node
+ end
+ end
+
+ seats
+ end
+
+ # Adds relation condition for left half of the bracket. This is used in
+ # double-elimination brackets primarily.
+ #
+ # @return [BracketTree::PositionalRelation]
+ def winners
+ @side = :left
+ return self
+ end
+
+ # Adds relation condition for right half of the bracket. This is used in
+ # double-elimination brackets primarily.
+ #
+ # @return [BracketTree::PositionalRelation]
+ def losers
+ @side = :right
+ return self
+ end
+
+ # Adds relation condition for a given round. This is represented by translating
+ # the total depth of the tree minus the number parameter.
+ #
+ # @param [Fixnum] round number
+ # @return [BracketTree::PositionalRelation]
+ def round number
+ @round = number
+ return self
+ end
+ end
+
+ module PositionalRelationDelegators
+ extend Forwardable
+
+ def relation
+ BracketTree::PositionalRelation.new(self)
+ end
+
+ def_delegators :relation, :winners, :losers, :round, :all, :first, :last, :seat
+ end
+end
View
@@ -186,4 +186,18 @@
bracket.at(2).payload.should == bracket.at(1).payload
end
end
+
+ describe 'positional relation hooks' do
+ let(:bracket) { BracketTree::Bracket::DoubleElimination.by_size 4 }
+
+ it 'delegates the query methods to a relation' do
+ relation = bracket.winners
+ relation.should be_a BracketTree::PositionalRelation
+ end
+
+ it 'delegates the accessor methods to a relation' do
+ nodes = bracket.winners.round(1).all
+ nodes.should have(4).nodes
+ end
+ end
end
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe BracketTree::PositionalRelation do
+ let(:bracket) { BracketTree::Bracket::DoubleElimination.by_size(4) }
+ let(:relation) { BracketTree::PositionalRelation.new(bracket) }
+ describe '#winners' do
+ it 'should set the side to left' do
+ relation.winners
+ relation.instance_variable_get(:@side).should == :left
+ end
+
+ it 'should return the same relation that called the method' do
+ r = relation.winners
+ r.should be relation
+ end
+ end
+
+ describe '#losers' do
+ it 'should set the side to right' do
+ relation.losers
+ relation.instance_variable_get(:@side).should == :right
+ end
+
+ it 'should return the same relation that called the method' do
+ r = relation.losers
+ r.should be relation
+ end
+ end
+
+ describe '#round' do
+ it 'should set the round to the passed parameter' do
+ relation.round(4)
+ relation.instance_variable_get(:@round).should == 4
+ end
+
+ it 'should return the same relation that called the method' do
+ r = relation.round(4)
+ r.should be relation
+ end
+ end
+
+ describe '#seat' do
+ it 'should return the Node that corresponds to the given seat number' do
+ node = relation.winners.round(1).seat(3)
+ node.should be_instance_of BracketTree::Node
+ end
+
+ it 'should return the current number to the given seat number' do
+ node = relation.winners.round(1).seat(3)
+ node.position.should == 5
+ end
+
+ it 'should return nil if the seat exceeds parameters' do
+ node = relation.winners.round(1).seat(9)
+ node.should be_nil
+ end
+ end
+
+ describe '#all' do
+ it 'should return an Array of Node objects' do
+ nodes = relation.winners.round(1).all
+ nodes.should be_instance_of(Array)
+ nodes.should have(4).nodes
+ nodes.map(&:class).uniq.first.should == BracketTree::Node
+ end
+
+ it 'should pull all sides of tree if a side is not picked' do
+ nodes = relation.round(1).all
+ nodes.should have(6).nodes
+ end
+
+ it 'should pull all rounds of a side if a round is not picked' do
+ nodes = relation.winners.all
+ nodes.should have(7).nodes
+ end
+
+ it 'should pull every node if neither round nor side is picked' do
+ nodes = relation.all
+ nodes.should have(13).nodes
+ end
+ end
+
+ describe '#first' do
+ it 'should return the first seat of the applicable nodes' do
+ node = relation.round(1).first
+ node.should be_instance_of(BracketTree::Node)
+ node.position.should == 1
+ end
+ end
+
+ describe '#last' do
+ it 'should return the last seat of the applicable nodes' do
+ node = relation.round(1).last
+ node.should be_instance_of(BracketTree::Node)
+ node.position.should == 13
+ end
+ end
+end

0 comments on commit 5728ae9

Please sign in to comment.