philiprehberger-tree 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bd37f9905975c79b4fa407d42276f950c4a267bf3133127f7c631139fe1d4b3e
4
+ data.tar.gz: b88f671ca4f70ff37dd929decb5fe063e0080bccbe4bd789c01140d48e4a9942
5
+ SHA512:
6
+ metadata.gz: 6b20812cb1d392ea095dbbe2a2752e76b037f739703ea696de6d79b0dcac69dc47a2210534470f0902191ac611d8c478a57eb85aee95d0a42e86b674f2429b06
7
+ data.tar.gz: b03f869248187d0e481b122500768bb69ed89587d5cd75653f46e070e737bb50e719fad2debd9d9191a6ede94afc0a35283d04a30bf1330ed6fb979fe91836eb
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this gem will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-22
11
+
12
+ ### Added
13
+ - Initial release
14
+ - Generic tree node with value, children, and parent tracking
15
+ - Depth-first (pre-order) and breadth-first traversal
16
+ - Node search with predicate block
17
+ - Path finding from root to target value
18
+ - Leaf node collection
19
+ - Tree metrics: depth, height, and size
20
+ - Hash serialization via `to_h`
21
+ - Visual tree printing via `print_tree`
22
+ - Add and remove child operations
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # philiprehberger-tree
2
+
3
+ [![Tests](https://github.com/philiprehberger/rb-tree/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-tree/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-tree.svg)](https://rubygems.org/gems/philiprehberger-tree)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/rb-tree)](LICENSE)
6
+
7
+ Generic tree data structure with traversal, search, and serialization
8
+
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "philiprehberger-tree"
19
+ ```
20
+
21
+ Or install directly:
22
+
23
+ ```bash
24
+ gem install philiprehberger-tree
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require "philiprehberger/tree"
31
+
32
+ root = Philiprehberger::Tree::Node.new('root')
33
+ child = root.add_child('child')
34
+ child.add_child('grandchild')
35
+
36
+ root.size # => 3
37
+ root.height # => 2
38
+ root.leaf? # => false
39
+ child.depth # => 1
40
+ ```
41
+
42
+ ### Traversal
43
+
44
+ ```ruby
45
+ root.each_dfs { |node| puts node.value } # depth-first pre-order
46
+ root.each_bfs { |node| puts node.value } # breadth-first
47
+ ```
48
+
49
+ ### Search and Path Finding
50
+
51
+ ```ruby
52
+ node = root.find { |n| n.value == 'grandchild' }
53
+ path = root.path_to('grandchild')
54
+ path.map(&:value) # => ['root', 'child', 'grandchild']
55
+ ```
56
+
57
+ ### Serialization
58
+
59
+ ```ruby
60
+ root.to_h
61
+ # => { value: 'root', children: [{ value: 'child', children: [...] }] }
62
+
63
+ puts root.print_tree
64
+ # root
65
+ # └── child
66
+ # └── grandchild
67
+ ```
68
+
69
+ ### Leaf Collection
70
+
71
+ ```ruby
72
+ root.leaves.map(&:value) # => ['grandchild']
73
+ ```
74
+
75
+ ## API
76
+
77
+ ### `Node`
78
+
79
+ | Method | Description |
80
+ |--------|-------------|
81
+ | `.new(value)` | Create a new tree node |
82
+ | `#add_child(value)` | Add a child node and return it |
83
+ | `#remove_child(value)` | Remove a child by value |
84
+ | `#children` | Array of child nodes |
85
+ | `#parent` | Parent node or nil |
86
+ | `#root?` | True if node has no parent |
87
+ | `#leaf?` | True if node has no children |
88
+ | `#depth` | Distance from root |
89
+ | `#height` | Height of subtree |
90
+ | `#size` | Total nodes in subtree |
91
+ | `#each_dfs` | Depth-first pre-order iteration |
92
+ | `#each_bfs` | Breadth-first iteration |
93
+ | `#find { \|n\| }` | Find first node matching predicate |
94
+ | `#path_to(value)` | Array of nodes from root to target |
95
+ | `#leaves` | All leaf nodes in subtree |
96
+ | `#to_h` | Serialize subtree to hash |
97
+ | `#print_tree` | Visual string representation |
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ bundle install
103
+ bundle exec rspec # Run tests
104
+ bundle exec rubocop # Check code style
105
+ ```
106
+
107
+ ## License
108
+
109
+ MIT
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module Tree
5
+ # A generic tree node with traversal, search, and serialization.
6
+ class Node
7
+ # @return [Object] the value stored in this node
8
+ attr_reader :value
9
+
10
+ # @return [Array<Node>] child nodes
11
+ attr_reader :children
12
+
13
+ # @return [Node, nil] parent node (nil for root)
14
+ attr_reader :parent
15
+
16
+ # Create a new tree node.
17
+ #
18
+ # @param value [Object] the value to store
19
+ def initialize(value)
20
+ @value = value
21
+ @children = []
22
+ @parent = nil
23
+ end
24
+
25
+ # Add a child node with the given value.
26
+ #
27
+ # @param value [Object] value for the new child node
28
+ # @return [Node] the newly created child node
29
+ def add_child(value)
30
+ child = value.is_a?(Node) ? value : Node.new(value)
31
+ child.instance_variable_set(:@parent, self)
32
+ @children << child
33
+ child
34
+ end
35
+
36
+ # Remove a child node by value.
37
+ #
38
+ # @param value [Object] value of the child to remove
39
+ # @return [Node, nil] the removed child node, or nil if not found
40
+ def remove_child(value)
41
+ index = @children.index { |c| c.value == value }
42
+ return nil unless index
43
+
44
+ child = @children.delete_at(index)
45
+ child.instance_variable_set(:@parent, nil)
46
+ child
47
+ end
48
+
49
+ # Check if this node is the root (has no parent).
50
+ #
51
+ # @return [Boolean]
52
+ def root?
53
+ @parent.nil?
54
+ end
55
+
56
+ # Check if this node is a leaf (has no children).
57
+ #
58
+ # @return [Boolean]
59
+ def leaf?
60
+ @children.empty?
61
+ end
62
+
63
+ # Return the depth of this node (distance from root).
64
+ #
65
+ # @return [Integer]
66
+ def depth
67
+ node = self
68
+ d = 0
69
+ while node.parent
70
+ d += 1
71
+ node = node.parent
72
+ end
73
+ d
74
+ end
75
+
76
+ # Return the height of the subtree rooted at this node.
77
+ #
78
+ # @return [Integer]
79
+ def height
80
+ return 0 if leaf?
81
+
82
+ 1 + @children.map(&:height).max
83
+ end
84
+
85
+ # Return the total number of nodes in the subtree rooted at this node.
86
+ #
87
+ # @return [Integer]
88
+ def size
89
+ 1 + @children.sum(&:size)
90
+ end
91
+
92
+ # Iterate depth-first (pre-order) over the subtree.
93
+ #
94
+ # @yield [Node] each node in depth-first order
95
+ # @return [Enumerator] if no block given
96
+ def each_dfs(&block)
97
+ return enum_for(:each_dfs) unless block
98
+
99
+ block.call(self)
100
+ @children.each { |child| child.each_dfs(&block) }
101
+ end
102
+
103
+ # Iterate breadth-first over the subtree.
104
+ #
105
+ # @yield [Node] each node in breadth-first order
106
+ # @return [Enumerator] if no block given
107
+ def each_bfs(&block)
108
+ return enum_for(:each_bfs) unless block
109
+
110
+ queue = [self]
111
+ until queue.empty?
112
+ node = queue.shift
113
+ block.call(node)
114
+ queue.concat(node.children)
115
+ end
116
+ end
117
+
118
+ # Find the first node matching the block.
119
+ #
120
+ # @yield [Node] predicate block
121
+ # @return [Node, nil] the first matching node, or nil
122
+ def find(&block)
123
+ return nil unless block
124
+
125
+ each_dfs { |node| return node if block.call(node) }
126
+ nil
127
+ end
128
+
129
+ # Return the path from root to the first node with the given value.
130
+ #
131
+ # @param value [Object] value to search for
132
+ # @return [Array<Node>, nil] array of nodes from root to target, or nil if not found
133
+ def path_to(value)
134
+ target = find { |n| n.value == value }
135
+ return nil unless target
136
+
137
+ path = []
138
+ node = target
139
+ while node
140
+ path.unshift(node)
141
+ node = node.parent
142
+ end
143
+ path
144
+ end
145
+
146
+ # Return all leaf nodes in the subtree.
147
+ #
148
+ # @return [Array<Node>]
149
+ def leaves
150
+ each_dfs.select(&:leaf?)
151
+ end
152
+
153
+ # Serialize the subtree to a hash.
154
+ #
155
+ # @return [Hash] hash with :value and :children keys
156
+ def to_h
157
+ {
158
+ value: @value,
159
+ children: @children.map(&:to_h)
160
+ }
161
+ end
162
+
163
+ # Print a visual representation of the tree.
164
+ #
165
+ # @param indent [String] prefix for indentation (used internally)
166
+ # @param last [Boolean] whether this is the last sibling (used internally)
167
+ # @return [String] the tree as a formatted string
168
+ def print_tree(indent: '', last: true)
169
+ lines = []
170
+ connector = root? ? '' : (last ? '└── ' : '├── ')
171
+ lines << "#{indent}#{connector}#{@value}"
172
+
173
+ child_indent = indent + (root? ? '' : (last ? ' ' : '│ '))
174
+ @children.each_with_index do |child, i|
175
+ lines << child.print_tree(indent: child_indent, last: i == @children.size - 1)
176
+ end
177
+
178
+ lines.join("\n")
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module Tree
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tree/version'
4
+ require_relative 'tree/node'
5
+
6
+ module Philiprehberger
7
+ module Tree
8
+ class Error < StandardError; end
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: philiprehberger-tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Rehberger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A generic tree data structure supporting depth-first and breadth-first
14
+ traversal, node search, path finding, and hash serialization. Each node tracks its
15
+ parent, children, depth, height, and size.
16
+ email:
17
+ - me@philiprehberger.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - README.md
25
+ - lib/philiprehberger/tree.rb
26
+ - lib/philiprehberger/tree/node.rb
27
+ - lib/philiprehberger/tree/version.rb
28
+ homepage: https://github.com/philiprehberger/rb-tree
29
+ licenses:
30
+ - MIT
31
+ metadata:
32
+ homepage_uri: https://github.com/philiprehberger/rb-tree
33
+ source_code_uri: https://github.com/philiprehberger/rb-tree
34
+ changelog_uri: https://github.com/philiprehberger/rb-tree/blob/main/CHANGELOG.md
35
+ bug_tracker_uri: https://github.com/philiprehberger/rb-tree/issues
36
+ rubygems_mfa_required: 'true'
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 3.1.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.5.22
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Generic tree data structure with traversal, search, and serialization
56
+ test_files: []