Skip to content
This repository
Browse code

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...
commit 5728ae9fe631d6e57c3b6c198c044ee419325667 1 parent ebac437
Andrew Nordman cadwallion authored
1  lib/bracket_tree.rb
... ... @@ -1,4 +1,5 @@
1 1 require 'json'
  2 +require 'bracket_tree/positional_relation'
2 3 require 'bracket_tree/match'
3 4 require 'bracket_tree/bracket'
4 5 require 'bracket_tree/template'
44 lib/bracket_tree/bracket/base.rb
@@ -26,6 +26,8 @@ module Bracket
26 26 class Base
27 27 class NoTemplateError < Exception ; end
28 28
  29 + include PositionalRelationDelegators
  30 +
29 31 class << self
30 32 def by_size size, options = {}
31 33 generate_from_template @template, size
@@ -55,11 +57,16 @@ def generate_from_template template, size
55 57 end
56 58
57 59 include Enumerable
58   - attr_accessor :root, :seed_order, :matches, :insertion_order
  60 + attr_accessor :root, :seed_order, :matches, :insertion_order, :depth
59 61
60 62 def initialize options = {}
61 63 @insertion_order = []
62 64 @matches = []
  65 + @depth = {
  66 + total: 0,
  67 + left: 0,
  68 + right: 0
  69 + }
63 70
64 71 if options[:matches]
65 72 options[:matches].each do |m|
@@ -81,22 +88,34 @@ def add position, data
81 88
82 89 if @root.nil?
83 90 @root = node
  91 + @depth[:total] = 1
  92 + @depth[:left] = 1
  93 + @depth[:right] = 1
84 94 else
85 95 current = @root
  96 + current_depth = 2
86 97 loop do
87 98 if node.position < current.position
88 99 if current.left.nil?
  100 + node.depth = current_depth
89 101 current.left = node
  102 +
  103 + depth_check current_depth, node.position
90 104 break
91 105 else
92 106 current = current.left
  107 + current_depth += 1
93 108 end
94 109 elsif node.position > current.position
95 110 if current.right.nil?
  111 + node.depth = current_depth
96 112 current.right = node
  113 +
  114 + depth_check current_depth, node.position
97 115 break
98 116 else
99 117 current = current.right
  118 + current_depth += 1
100 119 end
101 120 else
102 121 break
@@ -105,6 +124,12 @@ def add position, data
105 124 end
106 125 end
107 126
  127 + def depth_check depth, position
  128 + @depth[:total] = [depth, @depth[:total]].max
  129 + @depth[:left] = [depth, @depth[:left] ].max if position < @root.position
  130 + @depth[:right] = [depth, @depth[:right]].max if position > @root.position
  131 + end
  132 +
108 133 # Replaces the data at a given node position with new payload. This is useful
109 134 # for updating bracket data, replacing placeholders with actual data, seeding,
110 135 # etc..
@@ -212,15 +237,20 @@ def match_loser seat
212 237
213 238 def in_order(node, block)
214 239 if node
215   - unless node.left.nil?
216   - in_order(node.left, block)
217   - end
  240 + in_order(node.left, block) unless node.left.nil?
218 241
219 242 block.call(node)
220 243
221   - unless node.right.nil?
222   - in_order(node.right, block)
223   - end
  244 + in_order(node.right, block) unless node.right.nil?
  245 + end
  246 + end
  247 +
  248 + def top_down(node, &block)
  249 + if node
  250 + block.call(node)
  251 +
  252 + top_down(node.left, &block) unless node.left.nil?
  253 + top_down(node.right, &block) unless node.right.nil?
224 254 end
225 255 end
226 256 end
2  lib/bracket_tree/node.rb
... ... @@ -1,6 +1,6 @@
1 1 module BracketTree
2 2 class Node
3   - attr_accessor :left, :right, :position, :payload
  3 + attr_accessor :left, :right, :position, :payload, :depth
4 4
5 5 def initialize position, payload = nil
6 6 @position = position
162 lib/bracket_tree/positional_relation.rb
... ... @@ -0,0 +1,162 @@
  1 +require 'forwardable'
  2 +
  3 +module BracketTree
  4 + # PositionalRelation
  5 + #
  6 + # This class is a relational object used for constructing tree traversal when
  7 + # you do not know the exact position value that you are looking for. It uses
  8 + # two types of methods: relation condition methods and relation access methods.
  9 + #
  10 + # Relation Condition Methods
  11 + #
  12 + # Relation condition methods take the original `PositionalRelation` object and
  13 + # add conditions, returning the original object for easy chaining of relation
  14 + # conditions. Actual traversal does not happen with these methods.
  15 + #
  16 + # @example
  17 + # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
  18 + # relation = BracketTree::PositionalRelation.new(bracket) # => <BracketTree::PositionalRelation:0x007fa9431e1060>
  19 + # relation.winners # => <BracketTree::PositionalRelation:0x007fa9431e1060>
  20 + # relation.round(4) # => <BracketTree::PositionalRelation:0x007fa9431e1060>
  21 + #
  22 + # Relation conditions can be chained to create very specific or very broad
  23 + # options. Once you call a Relation Access Method, it will build based on the
  24 + # chain.
  25 + #
  26 + # @example Complex chaining of relations
  27 + # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
  28 + # relation = BracketTree::PositionalRelation.new(bracket)
  29 + # relation.winners.round(4).seat(3) # => <BracketTree::Node:0x01e37>
  30 + #
  31 + # Typically you will not directly instance BracketTree::RelationalPosition due
  32 + # to the need to pass in a bracket. Instead, bracket objects have access to these
  33 + # methods directly, and will return a BracketTree::PositionalRelation object.
  34 + #
  35 + # Relation Access Methods
  36 + #
  37 + # Relation access methods take the complete set of relation conditions and uses
  38 + # them as instructions for traversing the tree to collect the Node(s) requested.
  39 + #
  40 + # @example
  41 + # bracket = BracketTree::Bracket::DoubleElimination.by_size(4)
  42 + # bracket.winners.round(4).first # => <BracketTree::Node:0x007fa9271e73e0>
  43 + class PositionalRelation
  44 + attr_reader :bracket
  45 +
  46 + def initialize bracket
  47 + @bracket = bracket
  48 + end
  49 +
  50 + # Retrieves the first seat based on the stored relation conditions
  51 + #
  52 + # @return [BracketTree::Node, nil]
  53 + def first
  54 + seats = all
  55 + seats.first
  56 + end
  57 +
  58 + # Retrieves the last seat based on the stored relation conditions
  59 + #
  60 + # @return [BracketTree::Node, nil]
  61 + def last
  62 + seats = all
  63 + seats.last
  64 + end
  65 +
  66 + # Retrieves all seats based on the stored relation conditions
  67 + #
  68 + # @return [Array<BracketTree::Node>]
  69 + def all
  70 + if @side
  71 + if @round
  72 + seats = by_round @round, @side
  73 + else
  74 + side_root = @bracket.root.send(@side)
  75 + seats = []
  76 +
  77 + @bracket.top_down(side_root) do |node|
  78 + seats << node
  79 + end
  80 + end
  81 + else
  82 + if @round
  83 + seats = by_round(@round, :left) + by_round(@round, :right)
  84 + else
  85 + seats = []
  86 + @bracket.top_down(@bracket.root) do |node|
  87 + seats << node
  88 + end
  89 + end
  90 + end
  91 +
  92 + seats
  93 + end
  94 +
  95 + # Retrieves the `number`-th seat from the given relation conditions
  96 + #
  97 + # @param [Fixnum] seat number
  98 + # @return [BracketTree::Node, nil] the seat requested
  99 + def seat number
  100 + seats = all
  101 +
  102 + seats[number-1]
  103 + end
  104 +
  105 + # Retrieves an Array of Nodes for a given round on a given side
  106 + #
  107 + # @param [Fixnum] round to pull
  108 + # @param [Fixnum] side of the tree to pull from
  109 + # @return [Array] array of Nodes from the round
  110 + def by_round round, side
  111 + depth = @bracket.depth[side] - (round - 1)
  112 + seats = []
  113 +
  114 + side_root = @bracket.root.send(side)
  115 + @bracket.top_down(side_root) do |node|
  116 + if node.depth == depth
  117 + seats << node
  118 + end
  119 + end
  120 +
  121 + seats
  122 + end
  123 +
  124 + # Adds relation condition for left half of the bracket. This is used in
  125 + # double-elimination brackets primarily.
  126 + #
  127 + # @return [BracketTree::PositionalRelation]
  128 + def winners
  129 + @side = :left
  130 + return self
  131 + end
  132 +
  133 + # Adds relation condition for right half of the bracket. This is used in
  134 + # double-elimination brackets primarily.
  135 + #
  136 + # @return [BracketTree::PositionalRelation]
  137 + def losers
  138 + @side = :right
  139 + return self
  140 + end
  141 +
  142 + # Adds relation condition for a given round. This is represented by translating
  143 + # the total depth of the tree minus the number parameter.
  144 + #
  145 + # @param [Fixnum] round number
  146 + # @return [BracketTree::PositionalRelation]
  147 + def round number
  148 + @round = number
  149 + return self
  150 + end
  151 + end
  152 +
  153 + module PositionalRelationDelegators
  154 + extend Forwardable
  155 +
  156 + def relation
  157 + BracketTree::PositionalRelation.new(self)
  158 + end
  159 +
  160 + def_delegators :relation, :winners, :losers, :round, :all, :first, :last, :seat
  161 + end
  162 +end
14 spec/bracket_spec.rb
@@ -186,4 +186,18 @@
186 186 bracket.at(2).payload.should == bracket.at(1).payload
187 187 end
188 188 end
  189 +
  190 + describe 'positional relation hooks' do
  191 + let(:bracket) { BracketTree::Bracket::DoubleElimination.by_size 4 }
  192 +
  193 + it 'delegates the query methods to a relation' do
  194 + relation = bracket.winners
  195 + relation.should be_a BracketTree::PositionalRelation
  196 + end
  197 +
  198 + it 'delegates the accessor methods to a relation' do
  199 + nodes = bracket.winners.round(1).all
  200 + nodes.should have(4).nodes
  201 + end
  202 + end
189 203 end
98 spec/positional_relation_spec.rb
... ... @@ -0,0 +1,98 @@
  1 +require 'spec_helper'
  2 +
  3 +describe BracketTree::PositionalRelation do
  4 + let(:bracket) { BracketTree::Bracket::DoubleElimination.by_size(4) }
  5 + let(:relation) { BracketTree::PositionalRelation.new(bracket) }
  6 + describe '#winners' do
  7 + it 'should set the side to left' do
  8 + relation.winners
  9 + relation.instance_variable_get(:@side).should == :left
  10 + end
  11 +
  12 + it 'should return the same relation that called the method' do
  13 + r = relation.winners
  14 + r.should be relation
  15 + end
  16 + end
  17 +
  18 + describe '#losers' do
  19 + it 'should set the side to right' do
  20 + relation.losers
  21 + relation.instance_variable_get(:@side).should == :right
  22 + end
  23 +
  24 + it 'should return the same relation that called the method' do
  25 + r = relation.losers
  26 + r.should be relation
  27 + end
  28 + end
  29 +
  30 + describe '#round' do
  31 + it 'should set the round to the passed parameter' do
  32 + relation.round(4)
  33 + relation.instance_variable_get(:@round).should == 4
  34 + end
  35 +
  36 + it 'should return the same relation that called the method' do
  37 + r = relation.round(4)
  38 + r.should be relation
  39 + end
  40 + end
  41 +
  42 + describe '#seat' do
  43 + it 'should return the Node that corresponds to the given seat number' do
  44 + node = relation.winners.round(1).seat(3)
  45 + node.should be_instance_of BracketTree::Node
  46 + end
  47 +
  48 + it 'should return the current number to the given seat number' do
  49 + node = relation.winners.round(1).seat(3)
  50 + node.position.should == 5
  51 + end
  52 +
  53 + it 'should return nil if the seat exceeds parameters' do
  54 + node = relation.winners.round(1).seat(9)
  55 + node.should be_nil
  56 + end
  57 + end
  58 +
  59 + describe '#all' do
  60 + it 'should return an Array of Node objects' do
  61 + nodes = relation.winners.round(1).all
  62 + nodes.should be_instance_of(Array)
  63 + nodes.should have(4).nodes
  64 + nodes.map(&:class).uniq.first.should == BracketTree::Node
  65 + end
  66 +
  67 + it 'should pull all sides of tree if a side is not picked' do
  68 + nodes = relation.round(1).all
  69 + nodes.should have(6).nodes
  70 + end
  71 +
  72 + it 'should pull all rounds of a side if a round is not picked' do
  73 + nodes = relation.winners.all
  74 + nodes.should have(7).nodes
  75 + end
  76 +
  77 + it 'should pull every node if neither round nor side is picked' do
  78 + nodes = relation.all
  79 + nodes.should have(13).nodes
  80 + end
  81 + end
  82 +
  83 + describe '#first' do
  84 + it 'should return the first seat of the applicable nodes' do
  85 + node = relation.round(1).first
  86 + node.should be_instance_of(BracketTree::Node)
  87 + node.position.should == 1
  88 + end
  89 + end
  90 +
  91 + describe '#last' do
  92 + it 'should return the last seat of the applicable nodes' do
  93 + node = relation.round(1).last
  94 + node.should be_instance_of(BracketTree::Node)
  95 + node.position.should == 13
  96 + end
  97 + end
  98 +end

0 comments on commit 5728ae9

Please sign in to comment.
Something went wrong with that request. Please try again.