red-black-tree 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +155 -6
- data/lib/red-black-tree.rb +141 -40
- data/lib/red_black_tree/node/data_delegation.rb +17 -0
- data/lib/red_black_tree/node/leaf_node_comparable.rb +13 -0
- data/lib/red_black_tree/node/left_right_element_referencers.rb +2 -2
- data/lib/red_black_tree/node.rb +15 -6
- data/lib/red_black_tree/version.rb +1 -1
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2b536af387a65d00a6d05596a8b209b32b500abf5cf156d5ab7169ab0149f6b2
         | 
| 4 | 
            +
              data.tar.gz: 0bc6b9317ee4659228b201d5feb0c3146b356c5831ebe00a003d5d4f491020dc
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ea60c092e82dcecf019933df7348db451e10922437d4ac90c325fb1654ad5226738513b4a4d2e2ee45f19d2af86753c7f5932584a66ec0e90bf8c3ea0ce4836a
         | 
| 7 | 
            +
              data.tar.gz: c022d30679141753341dee36142b6690756d1797fe11e2dab34b4ddeeaafcc163693a3686bf4e2a324bd60951b869d9cd6d9496d49fed0c2a9fa4c7c3335751a
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,25 @@ | |
| 1 1 | 
             
            ## [Unreleased]
         | 
| 2 2 |  | 
| 3 | 
            +
            ## [0.1.3] - 2024-10-21
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Make `RedBlackTree#left_most_node` public
         | 
| 6 | 
            +
            - Add `RedBlackTree#traverse_pre_order`
         | 
| 7 | 
            +
            - Add `RedBlackTree#traverse_in_order`
         | 
| 8 | 
            +
            - Add `RedBlackTree#traverse_post_order`
         | 
| 9 | 
            +
            - Add `RedBlackTree#traverse_level_order`
         | 
| 10 | 
            +
            - Add `RedBlackTree#traverse`, alias of `RedBlackTree#traverse_in_order`
         | 
| 11 | 
            +
            - Extend `RedBlackTree#search` to accept a block
         | 
| 12 | 
            +
            - Delegate missing `RedBlackTree::Node` methods to its `#data`
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## [0.1.2] - 2024-09-08
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            - Fix a bunch of issues in `RedBlackTree#insert!` and `RedBlackTree#delete!` algorithms
         | 
| 17 | 
            +
            - Fix `RedBlackTree::LeafNode`s being marked red
         | 
| 18 | 
            +
            - Handle comparison with `RedBlackTree::LeafNode` in subclasses of `RedBlackTree::Node`
         | 
| 19 | 
            +
            - Add `RedBlackTree#include?`
         | 
| 20 | 
            +
            - Add `RedBlackTree#search`
         | 
| 21 | 
            +
            - Alias `RedBlackTree#left_most_node` as `RedBlackTree#min`
         | 
| 22 | 
            +
             | 
| 3 23 | 
             
            ## [0.1.1] - 2024-08-04
         | 
| 4 24 |  | 
| 5 25 | 
             
            - Update `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` in README
         | 
    
        data/README.md
    CHANGED
    
    | @@ -17,20 +17,17 @@ If bundler is not being used to manage dependencies, install the gem by executin | |
| 17 17 | 
             
            ```ruby
         | 
| 18 18 | 
             
            Work = Struct.new :min_latency, keyword_init: true
         | 
| 19 19 |  | 
| 20 | 
            +
            # Needs to be implemented by you
         | 
| 20 21 | 
             
            class WorkNode < RedBlackTree::Node
         | 
| 21 22 | 
             
              def <=> other
         | 
| 22 | 
            -
                 | 
| 23 | 
            -
                  1
         | 
| 24 | 
            -
                else
         | 
| 25 | 
            -
                  self.data.min_latency <=> other.data.min_latency
         | 
| 26 | 
            -
                end
         | 
| 23 | 
            +
                self.data.min_latency <=> other.data.min_latency
         | 
| 27 24 | 
             
              end
         | 
| 28 25 | 
             
            end
         | 
| 29 26 |  | 
| 30 27 | 
             
            tree = RedBlackTree.new
         | 
| 31 28 |  | 
| 32 | 
            -
            tree << WorkNode.new(Work.new min_latency: 1.2)
         | 
| 33 29 | 
             
            tree << WorkNode.new(Work.new min_latency: 0.8)
         | 
| 30 | 
            +
            tree << WorkNode.new(Work.new min_latency: 1.2)
         | 
| 34 31 | 
             
            tree << WorkNode.new(Work.new min_latency: 0.8)
         | 
| 35 32 | 
             
            tree << WorkNode.new(Work.new min_latency: 0.4)
         | 
| 36 33 |  | 
| @@ -40,6 +37,158 @@ until tree.empty? | |
| 40 37 | 
             
            end
         | 
| 41 38 | 
             
            ```
         | 
| 42 39 |  | 
| 40 | 
            +
            ## Performance
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            > [!NOTE]
         | 
| 43 | 
            +
            > Red-black trees are designed for specific use cases and are not intended as a general-purpose data structure. The
         | 
| 44 | 
            +
            comparisons below are provided merely to illustrate the performance characteristics of the gem. However, it is important
         | 
| 45 | 
            +
            to note that the benchmarks do not take into account the self-balancing nature of red-black trees.
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ### Sort and iterate 10,000 elements
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            ```ruby
         | 
| 50 | 
            +
            require 'benchmark/ips'
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            Work = Struct.new :min_latency, keyword_init: true
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            class WorkNode < RedBlackTree::Node
         | 
| 55 | 
            +
              def <=> other
         | 
| 56 | 
            +
                self.data.min_latency <=> other.data.min_latency
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            Benchmark.ips do |x|
         | 
| 63 | 
            +
              x.report("RedBlackTree") do
         | 
| 64 | 
            +
                tree = RedBlackTree.new
         | 
| 65 | 
            +
                sample_data.each { |work| tree << WorkNode.new(work); }
         | 
| 66 | 
            +
                tree.shift until tree.empty?
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              # 1:1 comparison
         | 
| 70 | 
            +
              x.report("Array (gradual sort)") do
         | 
| 71 | 
            +
                array = []
         | 
| 72 | 
            +
                sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
         | 
| 73 | 
            +
                array.shift until array.empty?
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              x.report("Array (single sort)") do
         | 
| 77 | 
            +
                array = []
         | 
| 78 | 
            +
                sample_data.each { |work| array << work; }
         | 
| 79 | 
            +
                array.sort_by!(&:min_latency)
         | 
| 80 | 
            +
                array.shift until array.empty?
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              x.compare!
         | 
| 84 | 
            +
            end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            #=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
         | 
| 87 | 
            +
            #=> Warming up --------------------------------------
         | 
| 88 | 
            +
            #=>         RedBlackTree     1.000 i/100ms
         | 
| 89 | 
            +
            #=> Array (gradual sort)     1.000 i/100ms
         | 
| 90 | 
            +
            #=>  Array (single sort)    78.000 i/100ms
         | 
| 91 | 
            +
            #=> Calculating -------------------------------------
         | 
| 92 | 
            +
            #=>         RedBlackTree      5.417 (± 0.0%) i/s  (184.61 ms/i) -     28.000 in   5.172532s
         | 
| 93 | 
            +
            #=> Array (gradual sort)      0.268 (± 0.0%) i/s     (3.74 s/i) -      2.000 in   7.473005s
         | 
| 94 | 
            +
            #=>  Array (single sort)    768.691 (± 2.2%) i/s    (1.30 ms/i) -      3.900k in   5.076337s
         | 
| 95 | 
            +
            #=>
         | 
| 96 | 
            +
            #=> Comparison:
         | 
| 97 | 
            +
            #=>  Array (single sort):      768.7 i/s
         | 
| 98 | 
            +
            #=>         RedBlackTree:        5.4 i/s - 141.91x  slower
         | 
| 99 | 
            +
            #=> Array (gradual sort):        0.3 i/s - 2872.03x  slower
         | 
| 100 | 
            +
            ```
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            ### Sort and search 10,000 elements
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ```ruby
         | 
| 105 | 
            +
            require 'benchmark/ips'
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            Work = Struct.new :min_latency, keyword_init: true
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            class WorkNode < RedBlackTree::Node
         | 
| 110 | 
            +
              def <=> other
         | 
| 111 | 
            +
                self.data.min_latency <=> other.data.min_latency
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            sample_data = 10_000.times.map { Work.new(min_latency: rand(1_000)) }
         | 
| 116 | 
            +
            search_sample = sample_data.sample
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            Benchmark.ips do |x|
         | 
| 119 | 
            +
              x.report("RedBlackTree#search") do
         | 
| 120 | 
            +
                tree = RedBlackTree.new
         | 
| 121 | 
            +
                sample_data.each { |work| tree << WorkNode.new(work); }
         | 
| 122 | 
            +
                raise unless tree.search search_sample
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
              # 1:1 comparison
         | 
| 126 | 
            +
              x.report("Array#find (gradual sort)") do
         | 
| 127 | 
            +
                array = []
         | 
| 128 | 
            +
                sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
         | 
| 129 | 
            +
                raise unless array.find { |work| work.min_latency == search_sample.min_latency }
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              x.report("Array#find (single sort)") do
         | 
| 133 | 
            +
                array = []
         | 
| 134 | 
            +
                sample_data.each { |work| array << work; }
         | 
| 135 | 
            +
                array.sort_by!(&:min_latency)
         | 
| 136 | 
            +
                raise unless array.find { |work| work.min_latency == search_sample.min_latency }
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
              # 1:1 comparison
         | 
| 140 | 
            +
              x.report("Array#bsearch (gradual sort)") do
         | 
| 141 | 
            +
                array = []
         | 
| 142 | 
            +
                sample_data.each { |work| array << work; array.sort_by!(&:min_latency); }
         | 
| 143 | 
            +
                raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              x.report("Array#bsearch (single sort)") do
         | 
| 147 | 
            +
                array = []
         | 
| 148 | 
            +
                sample_data.each { |work| array << work; }
         | 
| 149 | 
            +
                array.sort_by!(&:min_latency)
         | 
| 150 | 
            +
                raise unless array.bsearch { |work| search_sample.min_latency <= work.min_latency }
         | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
              x.compare!
         | 
| 154 | 
            +
            end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            #=> ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin24]
         | 
| 157 | 
            +
            #=> Warming up --------------------------------------
         | 
| 158 | 
            +
            #=>  RedBlackTree#search     1.000 i/100ms
         | 
| 159 | 
            +
            #=> Array#find (gradual sort)
         | 
| 160 | 
            +
            #=>                          1.000 i/100ms
         | 
| 161 | 
            +
            #=> Array#find (single sort)
         | 
| 162 | 
            +
            #=>                         69.000 i/100ms
         | 
| 163 | 
            +
            #=> Array#bsearch (gradual sort)
         | 
| 164 | 
            +
            #=>                          1.000 i/100ms
         | 
| 165 | 
            +
            #=> Array#bsearch (single sort)
         | 
| 166 | 
            +
            #=>                         89.000 i/100ms
         | 
| 167 | 
            +
            #=> Calculating -------------------------------------
         | 
| 168 | 
            +
            #=>  RedBlackTree#search     12.926 (± 0.0%) i/s   (77.36 ms/i) -     65.000 in   5.030736s
         | 
| 169 | 
            +
            #=> Array#find (gradual sort)
         | 
| 170 | 
            +
            #=>                           0.262 (± 0.0%) i/s     (3.81 s/i) -      2.000 in   7.623953s
         | 
| 171 | 
            +
            #=> Array#find (single sort)
         | 
| 172 | 
            +
            #=>                         690.631 (± 1.0%) i/s    (1.45 ms/i) -      3.519k in   5.095823s
         | 
| 173 | 
            +
            #=> Array#bsearch (gradual sort)
         | 
| 174 | 
            +
            #=>                           0.267 (± 0.0%) i/s     (3.75 s/i) -      2.000 in   7.492482s
         | 
| 175 | 
            +
            #=> Array#bsearch (single sort)
         | 
| 176 | 
            +
            #=>                         895.413 (± 1.7%) i/s    (1.12 ms/i) -      4.539k in   5.070590s
         | 
| 177 | 
            +
            #=>
         | 
| 178 | 
            +
            #=> Comparison:
         | 
| 179 | 
            +
            #=> Array#bsearch (single sort):      895.4 i/s
         | 
| 180 | 
            +
            #=> Array#find (single sort):         690.6 i/s - 1.30x  slower
         | 
| 181 | 
            +
            #=> RedBlackTree#search:               12.9 i/s - 69.27x  slower
         | 
| 182 | 
            +
            #=> Array#bsearch (gradual sort):       0.3 i/s - 3354.39x  slower
         | 
| 183 | 
            +
            #=> Array#find (gradual sort):          0.3 i/s - 3412.57x  slower
         | 
| 184 | 
            +
            ```
         | 
| 185 | 
            +
             | 
| 186 | 
            +
            ## WIP Features
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            - `RedBlackTree#max`
         | 
| 189 | 
            +
            - `RedBlackTree#height`
         | 
| 190 | 
            +
            - `RedBlackTree#clear`
         | 
| 191 | 
            +
             | 
| 43 192 | 
             
            ## Development
         | 
| 44 193 |  | 
| 45 194 | 
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the
         | 
    
        data/lib/red-black-tree.rb
    CHANGED
    
    | @@ -14,9 +14,9 @@ class RedBlackTree | |
| 14 14 | 
             
              # @return [RedBlackTree::Node, nil] the root node
         | 
| 15 15 | 
             
              attr_reader :root
         | 
| 16 16 |  | 
| 17 | 
            -
              # @private
         | 
| 18 17 | 
             
              # @return [RedBlackTree::Node, nil] the left most node
         | 
| 19 18 | 
             
              attr_reader :left_most_node
         | 
| 19 | 
            +
              alias_method :min, :left_most_node
         | 
| 20 20 |  | 
| 21 21 | 
             
              def initialize
         | 
| 22 22 | 
             
                @size = 0
         | 
| @@ -41,8 +41,13 @@ class RedBlackTree | |
| 41 41 | 
             
              #
         | 
| 42 42 | 
             
              # @return [RedBlackTree::Node, nil] the removed node
         | 
| 43 43 | 
             
              def shift
         | 
| 44 | 
            -
                 | 
| 45 | 
            -
             | 
| 44 | 
            +
                return unless @left_most_node
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                node = @left_most_node.dup
         | 
| 47 | 
            +
                node.colour = node.parent = node.left = node.right = nil
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                delete! @left_most_node
         | 
| 50 | 
            +
             | 
| 46 51 | 
             
                node
         | 
| 47 52 | 
             
              end
         | 
| 48 53 |  | 
| @@ -72,8 +77,8 @@ class RedBlackTree | |
| 72 77 | 
             
              #
         | 
| 73 78 | 
             
              # @private ideally this is only used internally e.g. in #<< which has context on the ideal location for the node
         | 
| 74 79 | 
             
              # @param node [RedBlackTree::Node] the node to be inserted
         | 
| 75 | 
            -
              # @param target_parent [RedBlackTree::Node | 
| 76 | 
            -
              # @param direction ["left", "right" | 
| 80 | 
            +
              # @param target_parent [RedBlackTree::Node] the parent under which the node should be inserted
         | 
| 81 | 
            +
              # @param direction ["left", "right"] the direction of the node relative to the parent
         | 
| 77 82 | 
             
              # @return [RedBlackTree] self
         | 
| 78 83 | 
             
              def insert! node, target_parent = nil, direction = nil
         | 
| 79 84 | 
             
                raise ArgumentError, "cannot insert leaf node" if node.instance_of? LeafNode
         | 
| @@ -85,8 +90,6 @@ class RedBlackTree | |
| 85 90 | 
             
                  raise ArgumentError, "Target parent already has #{direction} child" if (child = target_parent[direction]) && child.valid?
         | 
| 86 91 | 
             
                end
         | 
| 87 92 |  | 
| 88 | 
            -
                opp_direction = opposite_direction direction if direction
         | 
| 89 | 
            -
             | 
| 90 93 | 
             
                node.parent = nil
         | 
| 91 94 | 
             
                node.left = LeafNode.new
         | 
| 92 95 | 
             
                node.left.parent = node
         | 
| @@ -108,17 +111,16 @@ class RedBlackTree | |
| 108 111 | 
             
                      node.parent.parent.red!
         | 
| 109 112 | 
             
                      node = node.parent.parent
         | 
| 110 113 | 
             
                    else
         | 
| 111 | 
            -
                       | 
| 112 | 
            -
             | 
| 113 | 
            -
                        rotate_sub_tree! node, opp_direction
         | 
| 114 | 
            +
                      opp_direction = node.opposite_position
         | 
| 115 | 
            +
                      if node.parent.position == opp_direction
         | 
| 116 | 
            +
                        rotate_sub_tree! node.parent, opp_direction
         | 
| 117 | 
            +
                        node = node[opp_direction]
         | 
| 114 118 | 
             
                      end
         | 
| 115 119 |  | 
| 120 | 
            +
                      opp_direction = node.opposite_position
         | 
| 121 | 
            +
                      rotate_sub_tree! node.parent.parent, opp_direction
         | 
| 116 122 | 
             
                      node.parent.black!
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                      if node.parent.parent
         | 
| 119 | 
            -
                        node.parent.parent.red!
         | 
| 120 | 
            -
                        rotate_sub_tree! node.parent.parent, direction
         | 
| 121 | 
            -
                      end
         | 
| 123 | 
            +
                      node.parent[opp_direction].red!
         | 
| 122 124 | 
             
                    end
         | 
| 123 125 |  | 
| 124 126 | 
             
                    @root.black!
         | 
| @@ -140,6 +142,8 @@ class RedBlackTree | |
| 140 142 | 
             
              def delete! node
         | 
| 141 143 | 
             
                raise ArgumentError, "cannot delete leaf node" if node.instance_of? LeafNode
         | 
| 142 144 |  | 
| 145 | 
            +
                original_node = node
         | 
| 146 | 
            +
             | 
| 143 147 | 
             
                if node.children_are_valid?
         | 
| 144 148 | 
             
                  successor = node.left
         | 
| 145 149 | 
             
                  successor = successor.left until successor.left.leaf?
         | 
| @@ -160,19 +164,19 @@ class RedBlackTree | |
| 160 164 | 
             
                  if is_root? node
         | 
| 161 165 | 
             
                    @root = nil
         | 
| 162 166 | 
             
                  elsif node.red?
         | 
| 163 | 
            -
                     | 
| 164 | 
            -
                    node.swap_position_with! leaf
         | 
| 167 | 
            +
                    node.swap_position_with! LeafNode.new
         | 
| 165 168 | 
             
                  else
         | 
| 166 | 
            -
                    direction = node.position
         | 
| 167 | 
            -
                    opp_direction = opposite_direction direction
         | 
| 168 | 
            -
             | 
| 169 169 | 
             
                    loop do
         | 
| 170 | 
            -
                      if node.sibling.valid? && node.sibling.red?
         | 
| 170 | 
            +
                      if node.sibling && node.sibling.valid? && node.sibling.red?
         | 
| 171 171 | 
             
                        node.parent.red!
         | 
| 172 172 | 
             
                        node.sibling.black!
         | 
| 173 | 
            -
                        rotate_sub_tree! node.parent,  | 
| 173 | 
            +
                        rotate_sub_tree! node.parent, node.position
         | 
| 174 | 
            +
                      end
         | 
| 174 175 |  | 
| 175 | 
            -
             | 
| 176 | 
            +
                      if node.close_nephew && node.close_nephew.valid? && node.close_nephew.red?
         | 
| 177 | 
            +
                        node.sibling.red! unless node.sibling.leaf?
         | 
| 178 | 
            +
                        node.close_nephew.black!
         | 
| 179 | 
            +
                        rotate_sub_tree! node.sibling, node.opposite_position
         | 
| 176 180 | 
             
                      end
         | 
| 177 181 |  | 
| 178 182 | 
             
                      if node.distant_nephew && node.distant_nephew.valid? && node.distant_nephew.red?
         | 
| @@ -182,35 +186,30 @@ class RedBlackTree | |
| 182 186 | 
             
                        end
         | 
| 183 187 | 
             
                        node.parent.black!
         | 
| 184 188 | 
             
                        node.distant_nephew.black!
         | 
| 185 | 
            -
                        rotate_sub_tree! node.parent,  | 
| 189 | 
            +
                        rotate_sub_tree! node.parent, node.position
         | 
| 186 190 |  | 
| 187 191 | 
             
                        break
         | 
| 188 192 | 
             
                      end
         | 
| 189 193 |  | 
| 190 | 
            -
                      if node. | 
| 191 | 
            -
                        node.sibling.red!
         | 
| 192 | 
            -
                        node. | 
| 193 | 
            -
                        rotate_sub_tree! node.sibling, opp_direction
         | 
| 194 | 
            +
                      if node.parent && node.parent.red?
         | 
| 195 | 
            +
                        node.sibling.red! unless node.sibling.leaf?
         | 
| 196 | 
            +
                        node.parent.black!
         | 
| 194 197 |  | 
| 195 | 
            -
                         | 
| 198 | 
            +
                        break
         | 
| 196 199 | 
             
                      end
         | 
| 197 200 |  | 
| 198 | 
            -
                      if node. | 
| 201 | 
            +
                      if node.sibling && !node.sibling.leaf?
         | 
| 199 202 | 
             
                        node.sibling.red!
         | 
| 200 | 
            -
                        node.parent.black!
         | 
| 201 | 
            -
             | 
| 202 | 
            -
                        break
         | 
| 203 203 | 
             
                      end
         | 
| 204 204 |  | 
| 205 | 
            -
                      break
         | 
| 205 | 
            +
                      break unless node = node.parent
         | 
| 206 206 | 
             
                    end
         | 
| 207 207 |  | 
| 208 | 
            -
                     | 
| 209 | 
            -
                    node.swap_position_with! leaf
         | 
| 208 | 
            +
                    original_node.swap_position_with! LeafNode.new
         | 
| 210 209 | 
             
                  end
         | 
| 211 210 | 
             
                end
         | 
| 212 211 |  | 
| 213 | 
            -
                 | 
| 212 | 
            +
                original_node.validate_free!
         | 
| 214 213 |  | 
| 215 214 | 
             
                decrement_size!
         | 
| 216 215 | 
             
                update_left_most_node!
         | 
| @@ -218,12 +217,90 @@ class RedBlackTree | |
| 218 217 | 
             
                self
         | 
| 219 218 | 
             
              end
         | 
| 220 219 |  | 
| 221 | 
            -
               | 
| 220 | 
            +
              # Searches for a node which matches the given data/value.
         | 
| 221 | 
            +
              #
         | 
| 222 | 
            +
              # @param data [any] the data to search for
         | 
| 223 | 
            +
              # @yield [RedBlackTree::Node] the block to be used for comparison
         | 
| 224 | 
            +
              # @return [RedBlackTree::Node, nil] the matching node
         | 
| 225 | 
            +
              def search data = nil, &block
         | 
| 226 | 
            +
                if block_given?
         | 
| 227 | 
            +
                  raise ArgumentError, "provide either data or block, not both" if data
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  _search_by_block block, @root
         | 
| 230 | 
            +
                else
         | 
| 231 | 
            +
                  raise ArgumentError, "data must be provided for search" unless data
         | 
| 222 232 |  | 
| 223 | 
            -
             | 
| 224 | 
            -
                 | 
| 233 | 
            +
                  _search_by_data data, @root
         | 
| 234 | 
            +
                end
         | 
| 235 | 
            +
              end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
              # Returns true if there is a node which matches the given data/value, and false if there is not.
         | 
| 238 | 
            +
              #
         | 
| 239 | 
            +
              # @return [true, false]
         | 
| 240 | 
            +
              def include? data
         | 
| 241 | 
            +
                !!search(data)
         | 
| 242 | 
            +
              end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
              # Traverses the tree in pre-order and yields each node.
         | 
| 245 | 
            +
              #
         | 
| 246 | 
            +
              # @param node [RedBlackTree::Node] the node to start the traversal from
         | 
| 247 | 
            +
              # @yield [RedBlackTree::Node] the block to be executed for each node
         | 
| 248 | 
            +
              # @return [void]
         | 
| 249 | 
            +
              def traverse_pre_order node = @root, &block
         | 
| 250 | 
            +
                return if node.nil? || node.leaf?
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                block.call node
         | 
| 253 | 
            +
                traverse_pre_order node.left, &block
         | 
| 254 | 
            +
                traverse_pre_order node.right, &block
         | 
| 255 | 
            +
              end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
              # Traverses the tree in in-order and yields each node.
         | 
| 258 | 
            +
              #
         | 
| 259 | 
            +
              # @param node [RedBlackTree::Node] the node to start the traversal from
         | 
| 260 | 
            +
              # @yield [RedBlackTree::Node] the block to be executed for each node
         | 
| 261 | 
            +
              # @return [void]
         | 
| 262 | 
            +
              def traverse_in_order node = @root, &block
         | 
| 263 | 
            +
                return if node.nil? || node.leaf?
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                traverse_in_order node.left, &block
         | 
| 266 | 
            +
                block.call node
         | 
| 267 | 
            +
                traverse_in_order node.right, &block
         | 
| 268 | 
            +
              end
         | 
| 269 | 
            +
              alias_method :traverse, :traverse_in_order
         | 
| 270 | 
            +
             | 
| 271 | 
            +
              # Traverses the tree in post-order and yields each node.
         | 
| 272 | 
            +
              #
         | 
| 273 | 
            +
              # @param node [RedBlackTree::Node] the node to start the traversal from
         | 
| 274 | 
            +
              # @yield [RedBlackTree::Node] the block to be executed for each node
         | 
| 275 | 
            +
              # @return [void]
         | 
| 276 | 
            +
              def traverse_post_order node = @root, &block
         | 
| 277 | 
            +
                return if node.nil? || node.leaf?
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                traverse_post_order node.left, &block
         | 
| 280 | 
            +
                traverse_post_order node.right, &block
         | 
| 281 | 
            +
                block.call node
         | 
| 282 | 
            +
              end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
              # Traverses the tree in level-order and yields each node.
         | 
| 285 | 
            +
              #
         | 
| 286 | 
            +
              # @yield [RedBlackTree::Node] the block to be executed for each node
         | 
| 287 | 
            +
              # @return [void]
         | 
| 288 | 
            +
              def traverse_level_order &block
         | 
| 289 | 
            +
                return if @root.nil?
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                queue = [@root]
         | 
| 292 | 
            +
                until queue.empty?
         | 
| 293 | 
            +
                  node = queue.shift
         | 
| 294 | 
            +
                  next if node.nil? || node.leaf?
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                  block.call node
         | 
| 297 | 
            +
                  queue << node.left
         | 
| 298 | 
            +
                  queue << node.right
         | 
| 299 | 
            +
                end
         | 
| 225 300 | 
             
              end
         | 
| 226 301 |  | 
| 302 | 
            +
              private
         | 
| 303 | 
            +
             | 
| 227 304 | 
             
              # Rotates a (sub-)tree starting from the given node in the given direction.
         | 
| 228 305 | 
             
              #
         | 
| 229 306 | 
             
              # @param node [RedBlackTree::Node] the root node of the sub-tree
         | 
| @@ -250,6 +327,30 @@ class RedBlackTree | |
| 250 327 | 
             
                opp_direction_child
         | 
| 251 328 | 
             
              end
         | 
| 252 329 |  | 
| 330 | 
            +
              def _search_by_block block, node
         | 
| 331 | 
            +
                traverse node do |current|
         | 
| 332 | 
            +
                  next if current.leaf?
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                  return current if block.call current
         | 
| 335 | 
            +
                end
         | 
| 336 | 
            +
              end
         | 
| 337 | 
            +
             | 
| 338 | 
            +
              def _search_by_data data, node
         | 
| 339 | 
            +
                return if node.nil? || node.leaf?
         | 
| 340 | 
            +
                return node if data == node.data
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                mock_node = node.class.new data
         | 
| 343 | 
            +
                if mock_node >= node
         | 
| 344 | 
            +
                  _search_by_data data, node.right
         | 
| 345 | 
            +
                else
         | 
| 346 | 
            +
                  _search_by_data data, node.left
         | 
| 347 | 
            +
                end
         | 
| 348 | 
            +
              end
         | 
| 349 | 
            +
             | 
| 350 | 
            +
              def is_root? node
         | 
| 351 | 
            +
                node && @root && node.object_id == @root.object_id
         | 
| 352 | 
            +
              end
         | 
| 353 | 
            +
             | 
| 253 354 | 
             
              def increment_size!
         | 
| 254 355 | 
             
                @size += 1
         | 
| 255 356 | 
             
              end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class RedBlackTree
         | 
| 4 | 
            +
              module DataDelegation
         | 
| 5 | 
            +
                def method_missing method_name, *args, &block
         | 
| 6 | 
            +
                  if @data.respond_to? method_name
         | 
| 7 | 
            +
                    @data.public_send method_name, *args, &block
         | 
| 8 | 
            +
                  else
         | 
| 9 | 
            +
                    super
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def respond_to_missing? method_name, include_private = false
         | 
| 14 | 
            +
                  @data.respond_to? method_name, include_private || super
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -7,7 +7,7 @@ class RedBlackTree | |
| 7 7 | 
             
                    case direction
         | 
| 8 8 | 
             
                    when Node::LEFT then @left
         | 
| 9 9 | 
             
                    when Node::RIGHT then @right
         | 
| 10 | 
            -
                    else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
         | 
| 10 | 
            +
                    else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
         | 
| 11 11 | 
             
                    end
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| @@ -15,7 +15,7 @@ class RedBlackTree | |
| 15 15 | 
             
                    case direction
         | 
| 16 16 | 
             
                    when Node::LEFT then @left = node
         | 
| 17 17 | 
             
                    when Node::RIGHT then @right = node
         | 
| 18 | 
            -
                    else raise ArgumentError, "Direction must be one of #{DIRECTIONS}"
         | 
| 18 | 
            +
                    else raise ArgumentError, "Direction must be one of #{Implementation::DIRECTIONS}"
         | 
| 19 19 | 
             
                    end
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 | 
             
                end
         | 
    
        data/lib/red_black_tree/node.rb
    CHANGED
    
    | @@ -1,11 +1,20 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative "utils"
         | 
| 4 | 
            +
            require_relative "node/leaf_node_comparable"
         | 
| 5 | 
            +
            require_relative "node/data_delegation"
         | 
| 4 6 | 
             
            require_relative "node/left_right_element_referencers"
         | 
| 5 7 |  | 
| 6 8 | 
             
            class RedBlackTree
         | 
| 7 9 | 
             
              class Node
         | 
| 10 | 
            +
                class << self
         | 
| 11 | 
            +
                  def inherited subclass
         | 
| 12 | 
            +
                    subclass.prepend LeafNodeComparable
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 8 16 | 
             
                include Comparable
         | 
| 17 | 
            +
                include DataDelegation
         | 
| 9 18 |  | 
| 10 19 | 
             
                # @return [any] the data/value representing the node
         | 
| 11 20 | 
             
                attr_reader :data
         | 
| @@ -42,8 +51,8 @@ class RedBlackTree | |
| 42 51 | 
             
                  RIGHT = "right"
         | 
| 43 52 | 
             
                  DIRECTIONS = [LEFT, RIGHT].freeze
         | 
| 44 53 |  | 
| 45 | 
            -
                  attr_reader :colour
         | 
| 46 54 | 
             
                  attr_writer :data
         | 
| 55 | 
            +
                  attr_accessor :colour
         | 
| 47 56 | 
             
                  attr_accessor :tree, :parent
         | 
| 48 57 | 
             
                  attr_accessor :left, :right
         | 
| 49 58 | 
             
                  include LeftRightElementReferencers
         | 
| @@ -65,9 +74,9 @@ class RedBlackTree | |
| 65 74 | 
             
                  def position
         | 
| 66 75 | 
             
                    return unless @parent
         | 
| 67 76 |  | 
| 68 | 
            -
                    case self
         | 
| 69 | 
            -
                    when @parent.left then LEFT
         | 
| 70 | 
            -
                    when @parent.right then RIGHT
         | 
| 77 | 
            +
                    case self.object_id
         | 
| 78 | 
            +
                    when @parent.left.object_id then LEFT
         | 
| 79 | 
            +
                    when @parent.right.object_id then RIGHT
         | 
| 71 80 | 
             
                    else raise StructuralError, "Disowned by parent"
         | 
| 72 81 | 
             
                    end
         | 
| 73 82 | 
             
                  end
         | 
| @@ -140,13 +149,13 @@ class RedBlackTree | |
| 140 149 | 
             
                    self_position = position
         | 
| 141 150 | 
             
                    other_position = other_node.position
         | 
| 142 151 |  | 
| 143 | 
            -
                    if other_node.parent == self
         | 
| 152 | 
            +
                    if other_node.parent.object_id == self.object_id
         | 
| 144 153 | 
             
                      self[other_position] = other_node[other_position]
         | 
| 145 154 | 
             
                      other_node[other_position] = self
         | 
| 146 155 |  | 
| 147 156 | 
             
                      other_node.parent = @parent
         | 
| 148 157 | 
             
                      @parent = other_node
         | 
| 149 | 
            -
                    elsif other_node == @parent
         | 
| 158 | 
            +
                    elsif other_node.object_id == @parent.object_id
         | 
| 150 159 | 
             
                      other_node[self_position] = self[self_position]
         | 
| 151 160 | 
             
                      self[self_position] = other_node
         | 
| 152 161 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: red-black-tree
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Joshua Young
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-10-21 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies: []
         | 
| 13 13 | 
             
            description:
         | 
| 14 14 | 
             
            email:
         | 
| @@ -23,7 +23,9 @@ files: | |
| 23 23 | 
             
            - README.md
         | 
| 24 24 | 
             
            - lib/red-black-tree.rb
         | 
| 25 25 | 
             
            - lib/red_black_tree/node.rb
         | 
| 26 | 
            +
            - lib/red_black_tree/node/data_delegation.rb
         | 
| 26 27 | 
             
            - lib/red_black_tree/node/leaf_node.rb
         | 
| 28 | 
            +
            - lib/red_black_tree/node/leaf_node_comparable.rb
         | 
| 27 29 | 
             
            - lib/red_black_tree/node/left_right_element_referencers.rb
         | 
| 28 30 | 
             
            - lib/red_black_tree/utils.rb
         | 
| 29 31 | 
             
            - lib/red_black_tree/version.rb
         | 
| @@ -50,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 50 52 | 
             
                - !ruby/object:Gem::Version
         | 
| 51 53 | 
             
                  version: '0'
         | 
| 52 54 | 
             
            requirements: []
         | 
| 53 | 
            -
            rubygems_version: 3.5. | 
| 55 | 
            +
            rubygems_version: 3.5.16
         | 
| 54 56 | 
             
            signing_key:
         | 
| 55 57 | 
             
            specification_version: 4
         | 
| 56 58 | 
             
            summary: Red-Black Tree Data Structure for Ruby
         |