rubytree 0.9.7 → 2.0.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 +5 -5
- data/API-CHANGES.md +153 -0
- data/Gemfile +2 -13
- data/Gemfile.lock +60 -61
- data/History.md +410 -0
- data/LICENSE.md +1 -2
- data/README.md +24 -28
- data/Rakefile +65 -48
- data/TODO.org +19 -15
- data/examples/example_basic.rb +19 -12
- data/lib/rubytree.rb +3 -4
- data/lib/tree/binarytree.rb +27 -27
- data/lib/tree/tree_deps.rb +9 -12
- data/lib/tree/utils/hash_converter.rb +127 -121
- data/lib/tree/utils/json_converter.rb +81 -79
- data/lib/tree/utils/metrics_methods.rb +18 -48
- data/lib/tree/utils/path_methods.rb +15 -17
- data/lib/tree/utils/tree_merge_handler.rb +79 -80
- data/lib/tree/utils/utils.rb +9 -6
- data/lib/tree/version.rb +3 -5
- data/lib/tree.rb +194 -177
- data/rubytree.gemspec +67 -44
- data/spec/spec_helper.rb +5 -3
- data/spec/tree_spec.rb +136 -37
- data/test/run_test.rb +9 -8
- data/test/test_binarytree.rb +86 -105
- data/test/test_rubytree_require.rb +4 -5
- data/test/test_subclassed_node.rb +5 -26
- data/test/test_thread_and_fiber.rb +13 -16
- data/test/test_tree.rb +577 -657
- metadata +142 -55
- data/API-CHANGES.rdoc +0 -99
- data/History.rdoc +0 -303
- data/TAGS +0 -248
- data/gem_graph.png +0 -0
- data/lib/tree/utils/camel_case_method_handler.rb +0 -79
- data/setup.rb +0 -1585
data/lib/tree.rb
CHANGED
@@ -9,9 +9,7 @@
|
|
9
9
|
# Author:: Anupam Sengupta (anupamsg@gmail.com)
|
10
10
|
#
|
11
11
|
|
12
|
-
# Copyright (c) 2006-
|
13
|
-
#
|
14
|
-
# All rights reserved.
|
12
|
+
# Copyright (c) 2006-2022 Anupam Sengupta. All rights reserved.
|
15
13
|
#
|
16
14
|
# Redistribution and use in source and binary forms, with or without
|
17
15
|
# modification, are permitted provided that the following conditions are met:
|
@@ -38,6 +36,7 @@
|
|
38
36
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
39
37
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
40
38
|
#
|
39
|
+
# frozen_string_literal: true
|
41
40
|
|
42
41
|
require 'tree/tree_deps'
|
43
42
|
|
@@ -47,7 +46,6 @@ require 'tree/tree_deps'
|
|
47
46
|
# This module also acts as the namespace for all classes in the *RubyTree*
|
48
47
|
# package.
|
49
48
|
module Tree
|
50
|
-
|
51
49
|
# == TreeNode Class Description
|
52
50
|
#
|
53
51
|
# This class models the nodes for an *N-ary* tree data structure. The
|
@@ -83,12 +81,12 @@ module Tree
|
|
83
81
|
# {include:file:examples/example_basic.rb}
|
84
82
|
#
|
85
83
|
# @author Anupam Sengupta
|
84
|
+
# noinspection RubyTooManyMethodsInspection
|
86
85
|
class TreeNode
|
87
86
|
include Enumerable
|
88
87
|
include Comparable
|
89
88
|
include Tree::Utils::TreeMetricsHandler
|
90
89
|
include Tree::Utils::TreePathHandler
|
91
|
-
include Tree::Utils::CamelCaseMethodHandler
|
92
90
|
include Tree::Utils::JSONConverter
|
93
91
|
include Tree::Utils::TreeMergeHandler
|
94
92
|
include Tree::Utils::HashConverter
|
@@ -108,11 +106,11 @@ module Tree
|
|
108
106
|
# +content+ attribute for any non-unique node requirements.
|
109
107
|
#
|
110
108
|
# If you want to change the name, you probably want to call +rename+
|
111
|
-
# instead.
|
109
|
+
# instead. Note that +name=+ is a protected method.
|
112
110
|
#
|
113
111
|
# @see content
|
114
112
|
# @see rename
|
115
|
-
|
113
|
+
attr_accessor :name
|
116
114
|
|
117
115
|
# @!attribute [rw] content
|
118
116
|
# Content of this node. Can be +nil+. Note that there is no
|
@@ -132,38 +130,44 @@ module Tree
|
|
132
130
|
# @return [Tree::TreeNode] Root of the (sub)tree.
|
133
131
|
def root
|
134
132
|
root = self
|
135
|
-
root = root.parent
|
133
|
+
root = root.parent until root.root?
|
136
134
|
root
|
137
135
|
end
|
138
136
|
|
139
|
-
# @!attribute [r]
|
137
|
+
# @!attribute [r] root?
|
140
138
|
# Returns +true+ if this is a root node. Note that
|
141
139
|
# orphaned children will also be reported as root nodes.
|
142
140
|
#
|
143
141
|
# @return [Boolean] +true+ if this is a root node.
|
144
|
-
def
|
142
|
+
def root?
|
145
143
|
@parent.nil?
|
146
144
|
end
|
147
145
|
|
148
|
-
#
|
146
|
+
alias is_root? root? # @todo: Aliased for eventual replacement
|
147
|
+
|
148
|
+
# @!attribute [r] content?
|
149
149
|
# +true+ if this node has content.
|
150
150
|
#
|
151
151
|
# @return [Boolean] +true+ if the node has content.
|
152
|
-
def
|
152
|
+
def content?
|
153
153
|
@content != nil
|
154
154
|
end
|
155
155
|
|
156
|
-
#
|
156
|
+
alias has_content? content? # @todo: Aliased for eventual replacement
|
157
|
+
|
158
|
+
# @!attribute [r] leaf?
|
157
159
|
# +true+ if this node is a _leaf_ - i.e., one without
|
158
160
|
# any children.
|
159
161
|
#
|
160
162
|
# @return [Boolean] +true+ if this is a leaf node.
|
161
163
|
#
|
162
|
-
# @see #
|
163
|
-
def
|
164
|
-
!
|
164
|
+
# @see #children?
|
165
|
+
def leaf?
|
166
|
+
!children?
|
165
167
|
end
|
166
168
|
|
169
|
+
alias is_leaf? leaf? # @todo: Aliased for eventual replacement
|
170
|
+
|
167
171
|
# @!attribute [r] parentage
|
168
172
|
# An array of ancestors of this node in reversed order
|
169
173
|
# (the first element is the immediate parent of this node).
|
@@ -173,27 +177,29 @@ module Tree
|
|
173
177
|
# @return [Array<Tree::TreeNode>] An array of ancestors of this node
|
174
178
|
# @return [nil] if this is a root node.
|
175
179
|
def parentage
|
176
|
-
return nil if
|
180
|
+
return nil if root?
|
177
181
|
|
178
182
|
parentage_array = []
|
179
|
-
prev_parent =
|
180
|
-
while
|
183
|
+
prev_parent = parent
|
184
|
+
while prev_parent
|
181
185
|
parentage_array << prev_parent
|
182
186
|
prev_parent = prev_parent.parent
|
183
187
|
end
|
184
188
|
parentage_array
|
185
189
|
end
|
186
190
|
|
187
|
-
# @!attribute [r]
|
191
|
+
# @!attribute [r] children?
|
188
192
|
# +true+ if the this node has any child node.
|
189
193
|
#
|
190
194
|
# @return [Boolean] +true+ if child nodes exist.
|
191
195
|
#
|
192
|
-
# @see #
|
193
|
-
def
|
194
|
-
|
196
|
+
# @see #leaf?
|
197
|
+
def children?
|
198
|
+
!@children.empty?
|
195
199
|
end
|
196
200
|
|
201
|
+
alias has_children? children? # @todo: Aliased for eventual replacement
|
202
|
+
|
197
203
|
# @!group Node Creation
|
198
204
|
|
199
205
|
# Creates a new node with a name and optional content.
|
@@ -215,18 +221,14 @@ module Tree
|
|
215
221
|
#
|
216
222
|
# @see #[]
|
217
223
|
def initialize(name, content = nil)
|
218
|
-
raise ArgumentError,
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
"Using integer as node name."\
|
224
|
-
" Semantics of TreeNode[] may not be what you expect!"\
|
225
|
-
" #{name} #{content}"
|
226
|
-
end
|
224
|
+
raise ArgumentError, 'Node name HAS to be provided!' if name.nil?
|
225
|
+
|
226
|
+
name = name.to_s if name.is_a?(Integer)
|
227
|
+
@name = name
|
228
|
+
@content = content
|
227
229
|
|
228
|
-
|
229
|
-
@children_hash =
|
230
|
+
set_as_root!
|
231
|
+
@children_hash = {}
|
230
232
|
@children = []
|
231
233
|
end
|
232
234
|
|
@@ -235,7 +237,13 @@ module Tree
|
|
235
237
|
#
|
236
238
|
# @return [Tree::TreeNode] A copy of this node.
|
237
239
|
def detached_copy
|
238
|
-
|
240
|
+
cloned_content =
|
241
|
+
begin
|
242
|
+
@content&.clone
|
243
|
+
rescue TypeError
|
244
|
+
@content
|
245
|
+
end
|
246
|
+
self.class.new(@name, cloned_content)
|
239
247
|
end
|
240
248
|
|
241
249
|
# Returns a copy of entire (sub-)tree from this node.
|
@@ -253,50 +261,52 @@ module Tree
|
|
253
261
|
# Alias for {Tree::TreeNode#detached_subtree_copy}
|
254
262
|
#
|
255
263
|
# @see Tree::TreeNode#detached_subtree_copy
|
256
|
-
alias
|
264
|
+
alias dup detached_subtree_copy
|
257
265
|
|
258
266
|
# Returns a {marshal-dump}[http://ruby-doc.org/core-1.8.7/Marshal.html]
|
259
|
-
#
|
267
|
+
# representation of the (sub)tree rooted at this node.
|
260
268
|
#
|
261
269
|
def marshal_dump
|
262
|
-
|
270
|
+
collect(&:create_dump_rep)
|
263
271
|
end
|
264
272
|
|
265
273
|
# Creates a dump representation of this node and returns the same as
|
266
274
|
# a hash.
|
267
|
-
def create_dump_rep
|
268
|
-
{ :
|
269
|
-
:
|
270
|
-
:
|
271
|
-
}
|
275
|
+
def create_dump_rep # :nodoc:
|
276
|
+
{ name: @name,
|
277
|
+
parent: (root? ? nil : @parent.name),
|
278
|
+
content: Marshal.dump(@content) }
|
272
279
|
end
|
273
280
|
|
274
281
|
protected :create_dump_rep
|
275
282
|
|
276
|
-
# Loads a
|
283
|
+
# Loads a marshaled dump of a tree and returns the root node of the
|
277
284
|
# reconstructed tree. See the
|
278
285
|
# {Marshal}[http://ruby-doc.org/core-1.8.7/Marshal.html] class for
|
279
286
|
# additional details.
|
280
287
|
#
|
288
|
+
# NOTE: This is a potentially *unsafe* method with similar concerns as with
|
289
|
+
# the Marshal#load method, and should *not* be used with untrusted user
|
290
|
+
# provided data.
|
281
291
|
#
|
282
292
|
# @todo This method probably should be a class method. It currently clobbers
|
283
293
|
# self and makes itself the root.
|
284
294
|
#
|
285
295
|
def marshal_load(dumped_tree_array)
|
286
|
-
nodes = {
|
296
|
+
nodes = {}
|
287
297
|
dumped_tree_array.each do |node_hash|
|
288
298
|
name = node_hash[:name]
|
289
299
|
parent_name = node_hash[:parent]
|
290
300
|
content = Marshal.load(node_hash[:content])
|
291
301
|
|
292
|
-
if parent_name
|
293
|
-
nodes[name] = current_node =
|
302
|
+
if parent_name
|
303
|
+
nodes[name] = current_node = self.class.new(name, content)
|
294
304
|
nodes[parent_name].add current_node
|
295
305
|
else
|
296
306
|
# This is the root node, hence initialize self.
|
297
307
|
initialize(name, content)
|
298
308
|
|
299
|
-
nodes[name] = self
|
309
|
+
nodes[name] = self # Add self to the list of nodes
|
300
310
|
end
|
301
311
|
end
|
302
312
|
end
|
@@ -308,11 +318,9 @@ module Tree
|
|
308
318
|
#
|
309
319
|
# @return [String] A string representation of the node.
|
310
320
|
def to_s
|
311
|
-
"Node Name: #{@name}"
|
312
|
-
"
|
313
|
-
"
|
314
|
-
" Children: #{@children.length}" +
|
315
|
-
" Total Nodes: #{size()}"
|
321
|
+
"Node Name: #{@name} Content: #{@content.to_s || '<Empty>'} " \
|
322
|
+
"Parent: #{root? ? '<None>' : @parent.name.to_s} " \
|
323
|
+
"Children: #{@children.length} Total Nodes: #{size}"
|
316
324
|
end
|
317
325
|
|
318
326
|
# @!group Structure Modification
|
@@ -354,7 +362,7 @@ module Tree
|
|
354
362
|
#
|
355
363
|
# -children.size..children.size
|
356
364
|
#
|
357
|
-
# This is to prevent +nil+ nodes being created as children if a non-
|
365
|
+
# This is to prevent +nil+ nodes being created as children if a non-existent
|
358
366
|
# position is used.
|
359
367
|
#
|
360
368
|
# If the new node being added has an existing parent node, then it will be
|
@@ -380,38 +388,37 @@ module Tree
|
|
380
388
|
# @see #<<
|
381
389
|
def add(child, at_index = -1)
|
382
390
|
# Only handles the immediate child scenario
|
383
|
-
raise ArgumentError,
|
384
|
-
|
385
|
-
raise ArgumentError,
|
386
|
-
|
387
|
-
raise ArgumentError,
|
388
|
-
|
389
|
-
|
390
|
-
# Lazy mans unique test, won't test if children of child are unique in
|
391
|
+
raise ArgumentError, 'Attempting to add a nil node' unless child
|
392
|
+
|
393
|
+
raise ArgumentError, 'Attempting add node to itself' if equal?(child)
|
394
|
+
|
395
|
+
raise ArgumentError, 'Attempting add root as a child' if child.equal?(root)
|
396
|
+
|
397
|
+
# Lazy man's unique test, won't test if children of child are unique in
|
391
398
|
# this tree too.
|
392
399
|
raise "Child #{child.name} already added!"\
|
393
400
|
if @children_hash.include?(child.name)
|
394
401
|
|
395
|
-
child.parent
|
402
|
+
child.parent&.remove! child # Detach from the old parent
|
396
403
|
|
397
404
|
if insertion_range.include?(at_index)
|
398
405
|
@children.insert(at_index, child)
|
399
406
|
else
|
400
|
-
raise
|
407
|
+
raise 'Attempting to insert a child at a non-existent location'\
|
401
408
|
" (#{at_index}) "\
|
402
|
-
|
409
|
+
'when only positions from '\
|
403
410
|
"#{insertion_range.min} to #{insertion_range.max} exist."
|
404
411
|
end
|
405
412
|
|
406
|
-
@children_hash[child.name]
|
413
|
+
@children_hash[child.name] = child
|
407
414
|
child.parent = self
|
408
|
-
|
415
|
+
child
|
409
416
|
end
|
410
417
|
|
411
418
|
# Return a range of valid insertion positions. Used in the #add method.
|
412
419
|
def insertion_range
|
413
420
|
max = @children.size
|
414
|
-
min = -(max+1)
|
421
|
+
min = -(max + 1)
|
415
422
|
min..max
|
416
423
|
end
|
417
424
|
|
@@ -426,8 +433,8 @@ module Tree
|
|
426
433
|
def rename(new_name)
|
427
434
|
old_name = @name
|
428
435
|
|
429
|
-
if
|
430
|
-
self.name=
|
436
|
+
if root?
|
437
|
+
self.name = new_name
|
431
438
|
else
|
432
439
|
@parent.rename_child old_name, new_name
|
433
440
|
end
|
@@ -444,20 +451,10 @@ module Tree
|
|
444
451
|
# pass a String (Integer names may cause *surprises*)
|
445
452
|
def rename_child(old_name, new_name)
|
446
453
|
raise ArgumentError, "Invalid child name specified: #{old_name}"\
|
447
|
-
unless @children_hash.
|
454
|
+
unless @children_hash.key?(old_name)
|
448
455
|
|
449
456
|
@children_hash[new_name] = @children_hash.delete(old_name)
|
450
|
-
@children_hash[new_name].name=
|
451
|
-
end
|
452
|
-
|
453
|
-
# Protected method to set the name of this node.
|
454
|
-
# This method should *NOT* be invoked by client code.
|
455
|
-
#
|
456
|
-
# @param [Object] new_name The node Name to set.
|
457
|
-
#
|
458
|
-
# @return [Object] The new name.
|
459
|
-
def name=(new_name)
|
460
|
-
@name = new_name
|
457
|
+
@children_hash[new_name].name = new_name
|
461
458
|
end
|
462
459
|
|
463
460
|
# Replaces the specified child node with another child node on this node.
|
@@ -472,7 +469,7 @@ module Tree
|
|
472
469
|
old_child = remove! old_child
|
473
470
|
add new_child, child_index
|
474
471
|
|
475
|
-
|
472
|
+
old_child
|
476
473
|
end
|
477
474
|
|
478
475
|
# Replaces the node with another node
|
@@ -513,7 +510,7 @@ module Tree
|
|
513
510
|
# @param [Tree::TreeNode] parent The parent node.
|
514
511
|
#
|
515
512
|
# @return [Tree::TreeNode] The parent node.
|
516
|
-
def parent=(parent)
|
513
|
+
def parent=(parent) # :nodoc:
|
517
514
|
@parent = parent
|
518
515
|
@node_depth = nil
|
519
516
|
end
|
@@ -530,7 +527,7 @@ module Tree
|
|
530
527
|
#
|
531
528
|
# @see #remove_all!
|
532
529
|
def remove_from_parent!
|
533
|
-
@parent.remove!(self) unless
|
530
|
+
@parent.remove!(self) unless root?
|
534
531
|
end
|
535
532
|
|
536
533
|
# Removes all children from this node. If an independent reference exists to
|
@@ -542,7 +539,7 @@ module Tree
|
|
542
539
|
# @see #remove!
|
543
540
|
# @see #remove_from_parent!
|
544
541
|
def remove_all!
|
545
|
-
@children.each
|
542
|
+
@children.each(&:remove_all!)
|
546
543
|
|
547
544
|
@children_hash.clear
|
548
545
|
@children.clear
|
@@ -552,7 +549,7 @@ module Tree
|
|
552
549
|
# Protected method which sets this node as a root node.
|
553
550
|
#
|
554
551
|
# @return +nil+.
|
555
|
-
def set_as_root!
|
552
|
+
def set_as_root! # :nodoc:
|
556
553
|
self.parent = nil
|
557
554
|
end
|
558
555
|
|
@@ -563,7 +560,7 @@ module Tree
|
|
563
560
|
# The nodes become immutable after this operation. In effect, the entire tree's
|
564
561
|
# structure and contents become _read-only_ and cannot be changed.
|
565
562
|
def freeze_tree!
|
566
|
-
each
|
563
|
+
each(&:freeze)
|
567
564
|
end
|
568
565
|
|
569
566
|
# @!endgroup
|
@@ -574,46 +571,31 @@ module Tree
|
|
574
571
|
#
|
575
572
|
# - If the +name+ argument is an _Integer_, then the in-sequence
|
576
573
|
# array of children is accessed using the argument as the
|
577
|
-
# *index* (zero-based).
|
578
|
-
# +num_as_name+ argument is +true+, then the +name+ is used
|
579
|
-
# literally as a name, and *NOT* as an *index*
|
574
|
+
# *index* (zero-based).
|
580
575
|
#
|
581
576
|
# - If the +name+ argument is *NOT* an _Integer_, then it is taken to
|
582
577
|
# be the *name* of the child node to be returned.
|
583
578
|
#
|
584
|
-
#
|
585
|
-
#
|
586
|
-
# redundant use of the +num_as_name+ flag.)
|
579
|
+
# - To use an _Integer_ as the name, convert it to a _String_ first using
|
580
|
+
# +<integer>.to_s+.
|
587
581
|
#
|
588
582
|
# @param [String|Number] name_or_index Name of the child, or its
|
589
583
|
# positional index in the array of child nodes.
|
590
584
|
#
|
591
|
-
# @param [Boolean] num_as_name Whether to treat the +Integer+
|
592
|
-
# +name+ argument as an actual name, and *NOT* as an _index_ to
|
593
|
-
# the children array.
|
594
|
-
#
|
595
585
|
# @return [Tree::TreeNode] the requested child node. If the index
|
596
586
|
# in not in range, or the name is not present, then a +nil+
|
597
587
|
# is returned.
|
598
588
|
#
|
599
|
-
# @note The use of +Integer+ names is allowed by using the optional
|
600
|
-
# +num_as_name+ flag.
|
601
|
-
#
|
602
589
|
# @raise [ArgumentError] Raised if the +name_or_index+ argument is +nil+.
|
603
590
|
#
|
604
591
|
# @see #add
|
605
592
|
# @see #initialize
|
606
|
-
def [](name_or_index
|
607
|
-
raise ArgumentError,
|
608
|
-
"Name_or_index needs to be provided!" if name_or_index == nil
|
593
|
+
def [](name_or_index)
|
594
|
+
raise ArgumentError, 'Name_or_index needs to be provided!' if name_or_index.nil?
|
609
595
|
|
610
|
-
if name_or_index.
|
596
|
+
if name_or_index.is_a?(Integer)
|
611
597
|
@children[name_or_index]
|
612
598
|
else
|
613
|
-
if num_as_name and not name_or_index.kind_of?(Integer)
|
614
|
-
warn StandardWarning,
|
615
|
-
"Redundant use of the `num_as_name` flag for non-integer node name"
|
616
|
-
end
|
617
599
|
@children_hash[name_or_index]
|
618
600
|
end
|
619
601
|
end
|
@@ -631,22 +613,22 @@ module Tree
|
|
631
613
|
#
|
632
614
|
# @return [Tree::TreeNode] this node, if a block if given
|
633
615
|
# @return [Enumerator] an enumerator on this tree, if a block is *not* given
|
634
|
-
|
635
|
-
|
636
|
-
|
616
|
+
# noinspection RubyUnusedLocalVariable
|
617
|
+
def each # :yields: node
|
618
|
+
return to_enum unless block_given?
|
637
619
|
|
638
|
-
node_stack = [self]
|
620
|
+
node_stack = [self] # Start with this node
|
639
621
|
|
640
622
|
until node_stack.empty?
|
641
|
-
current = node_stack.shift
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
623
|
+
current = node_stack.shift # Pop the top-most node
|
624
|
+
next unless current # Might be 'nil' (esp. for binary trees)
|
625
|
+
|
626
|
+
yield current # and process it
|
627
|
+
# Stack children of the current node at top of the stack
|
628
|
+
node_stack = current.children.concat(node_stack)
|
647
629
|
end
|
648
630
|
|
649
|
-
|
631
|
+
self if block_given?
|
650
632
|
end
|
651
633
|
|
652
634
|
# Traverses the (sub)tree rooted at this node in pre-ordered sequence.
|
@@ -659,7 +641,7 @@ module Tree
|
|
659
641
|
#
|
660
642
|
# @return [Tree::TreeNode] this node, if a block if given
|
661
643
|
# @return [Enumerator] an enumerator on this tree, if a block is *not* given
|
662
|
-
def preordered_each(&block)
|
644
|
+
def preordered_each(&block) # :yields: node
|
663
645
|
each(&block)
|
664
646
|
end
|
665
647
|
|
@@ -671,30 +653,31 @@ module Tree
|
|
671
653
|
# @see #breadth_each
|
672
654
|
# @return [Tree::TreeNode] this node, if a block if given
|
673
655
|
# @return [Enumerator] an enumerator on this tree, if a block is *not* given
|
674
|
-
|
675
|
-
|
656
|
+
# noinspection RubyUnusedLocalVariable
|
657
|
+
def postordered_each
|
658
|
+
return to_enum(:postordered_each) unless block_given?
|
676
659
|
|
677
660
|
# Using a marked node in order to skip adding the children of nodes that
|
678
661
|
# have already been visited. This allows the stack depth to be controlled,
|
679
662
|
# and also allows stateful backtracking.
|
680
|
-
|
681
|
-
node_stack = [
|
663
|
+
marked_node = Struct.new(:node, :visited)
|
664
|
+
node_stack = [marked_node.new(self, false)] # Start with self
|
682
665
|
|
683
666
|
until node_stack.empty?
|
684
667
|
peek_node = node_stack[0]
|
685
|
-
if peek_node.node.
|
668
|
+
if peek_node.node.children? && !peek_node.visited
|
686
669
|
peek_node.visited = true
|
687
670
|
# Add the children to the stack. Use the marking structure.
|
688
671
|
marked_children =
|
689
|
-
peek_node.node.children.map {|node|
|
672
|
+
peek_node.node.children.map { |node| marked_node.new(node, false) }
|
690
673
|
node_stack = marked_children.concat(node_stack)
|
691
674
|
next
|
692
675
|
else
|
693
|
-
yield node_stack.shift.node
|
676
|
+
yield node_stack.shift.node # Pop and yield the current node
|
694
677
|
end
|
695
678
|
end
|
696
679
|
|
697
|
-
|
680
|
+
self if block_given?
|
698
681
|
end
|
699
682
|
|
700
683
|
# Performs breadth-first traversal of the (sub)tree rooted at this node. The
|
@@ -708,10 +691,11 @@ module Tree
|
|
708
691
|
#
|
709
692
|
# @return [Tree::TreeNode] this node, if a block if given
|
710
693
|
# @return [Enumerator] an enumerator on this tree, if a block is *not* given
|
711
|
-
|
712
|
-
|
694
|
+
# noinspection RubyUnusedLocalVariable
|
695
|
+
def breadth_each
|
696
|
+
return to_enum(:breadth_each) unless block_given?
|
713
697
|
|
714
|
-
node_queue = [self]
|
698
|
+
node_queue = [self] # Create a queue with self as the initial entry
|
715
699
|
|
716
700
|
# Use a queue to do breadth traversal
|
717
701
|
until node_queue.empty?
|
@@ -721,7 +705,7 @@ module Tree
|
|
721
705
|
node_to_traverse.children { |child| node_queue.push child }
|
722
706
|
end
|
723
707
|
|
724
|
-
|
708
|
+
self if block_given?
|
725
709
|
end
|
726
710
|
|
727
711
|
# An array of all the immediate children of this node. The child
|
@@ -736,12 +720,12 @@ module Tree
|
|
736
720
|
#
|
737
721
|
# @return [Array<Tree::TreeNode>] An array of the child nodes, if no block
|
738
722
|
# is given.
|
739
|
-
def children
|
723
|
+
def children(&block)
|
740
724
|
if block_given?
|
741
|
-
@children.each
|
742
|
-
|
725
|
+
@children.each(&block)
|
726
|
+
self
|
743
727
|
else
|
744
|
-
|
728
|
+
@children.clone
|
745
729
|
end
|
746
730
|
end
|
747
731
|
|
@@ -758,12 +742,35 @@ module Tree
|
|
758
742
|
#
|
759
743
|
# @return [Tree::TreeNode] this node, if a block if given
|
760
744
|
# @return [Array<Tree::TreeNode>] An array of the leaf nodes
|
761
|
-
|
745
|
+
# noinspection RubyUnusedLocalVariable
|
746
|
+
def each_leaf
|
747
|
+
if block_given?
|
748
|
+
each { |node| yield(node) if node.leaf? }
|
749
|
+
self
|
750
|
+
else
|
751
|
+
self.select(&:leaf?)
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
# Yields every level of the (sub)tree rooted at this node to the
|
756
|
+
# specified block.
|
757
|
+
#
|
758
|
+
# Will yield this node as well since it is considered the first level.
|
759
|
+
#
|
760
|
+
# @yieldparam level [Array<Tree::TreeNode>] All nodes in the level
|
761
|
+
#
|
762
|
+
# @return [Tree::TreeNode] this node, if a block if given
|
763
|
+
# @return [Enumerator] an enumerator on this tree, if a block is *not* given
|
764
|
+
def each_level
|
762
765
|
if block_given?
|
763
|
-
|
764
|
-
|
766
|
+
level = [self]
|
767
|
+
until level.empty?
|
768
|
+
yield level
|
769
|
+
level = level.map(&:children).flatten
|
770
|
+
end
|
771
|
+
self
|
765
772
|
else
|
766
|
-
|
773
|
+
each
|
767
774
|
end
|
768
775
|
end
|
769
776
|
|
@@ -776,7 +783,7 @@ module Tree
|
|
776
783
|
#
|
777
784
|
# @return [Tree::TreeNode] The first child, or +nil+ if none is present.
|
778
785
|
def first_child
|
779
|
-
children.first
|
786
|
+
@children.first
|
780
787
|
end
|
781
788
|
|
782
789
|
# Last child of this node.
|
@@ -784,7 +791,7 @@ module Tree
|
|
784
791
|
#
|
785
792
|
# @return [Tree::TreeNode] The last child, or +nil+ if none is present.
|
786
793
|
def last_child
|
787
|
-
children.last
|
794
|
+
@children.last
|
788
795
|
end
|
789
796
|
|
790
797
|
# @!group Navigating the Sibling Nodes
|
@@ -799,22 +806,24 @@ module Tree
|
|
799
806
|
#
|
800
807
|
# @return [Tree::TreeNode] The first sibling node.
|
801
808
|
#
|
802
|
-
# @see #
|
809
|
+
# @see #first_sibling?
|
803
810
|
# @see #last_sibling
|
804
811
|
def first_sibling
|
805
|
-
|
812
|
+
root? ? self : parent.children.first
|
806
813
|
end
|
807
814
|
|
808
815
|
# Returns +true+ if this node is the first sibling at its level.
|
809
816
|
#
|
810
817
|
# @return [Boolean] +true+ if this is the first sibling.
|
811
818
|
#
|
812
|
-
# @see #
|
819
|
+
# @see #last_sibling?
|
813
820
|
# @see #first_sibling
|
814
|
-
def
|
821
|
+
def first_sibling?
|
815
822
|
first_sibling == self
|
816
823
|
end
|
817
824
|
|
825
|
+
alias is_first_sibling? first_sibling? # @todo: Aliased for eventual replacement
|
826
|
+
|
818
827
|
# Last sibling of this node. If this is the root node, then returns
|
819
828
|
# itself.
|
820
829
|
#
|
@@ -825,22 +834,24 @@ module Tree
|
|
825
834
|
#
|
826
835
|
# @return [Tree::TreeNode] The last sibling node.
|
827
836
|
#
|
828
|
-
# @see #
|
837
|
+
# @see #last_sibling?
|
829
838
|
# @see #first_sibling
|
830
839
|
def last_sibling
|
831
|
-
|
840
|
+
root? ? self : parent.children.last
|
832
841
|
end
|
833
842
|
|
834
843
|
# Returns +true+ if this node is the last sibling at its level.
|
835
844
|
#
|
836
845
|
# @return [Boolean] +true+ if this is the last sibling.
|
837
846
|
#
|
838
|
-
# @see #
|
847
|
+
# @see #first_sibling?
|
839
848
|
# @see #last_sibling
|
840
|
-
def
|
849
|
+
def last_sibling?
|
841
850
|
last_sibling == self
|
842
851
|
end
|
843
852
|
|
853
|
+
alias is_last_sibling? last_sibling? # @todo: Aliased for eventual replacement
|
854
|
+
|
844
855
|
# An array of siblings for this node. This node is excluded.
|
845
856
|
#
|
846
857
|
# If a block is provided, yields each of the sibling nodes to the block.
|
@@ -858,12 +869,14 @@ module Tree
|
|
858
869
|
def siblings
|
859
870
|
if block_given?
|
860
871
|
parent.children.each { |sibling| yield sibling if sibling != self }
|
861
|
-
|
872
|
+
self
|
862
873
|
else
|
863
|
-
return [] if
|
874
|
+
return [] if root?
|
875
|
+
|
864
876
|
siblings = []
|
865
|
-
parent.children
|
866
|
-
|
877
|
+
parent.children do |my_sibling|
|
878
|
+
siblings << my_sibling if my_sibling != self
|
879
|
+
end
|
867
880
|
siblings
|
868
881
|
end
|
869
882
|
end
|
@@ -875,10 +888,12 @@ module Tree
|
|
875
888
|
# @return [Boolean] +true+ if this is the only child of its parent.
|
876
889
|
#
|
877
890
|
# @see #siblings
|
878
|
-
def
|
879
|
-
|
891
|
+
def only_child?
|
892
|
+
root? ? true : parent.children.size == 1
|
880
893
|
end
|
881
894
|
|
895
|
+
alias is_only_child? only_child? # @todo: Aliased for eventual replacement
|
896
|
+
|
882
897
|
# Next sibling for this node.
|
883
898
|
# The _next_ node is defined as the node to right of this node.
|
884
899
|
#
|
@@ -890,10 +905,10 @@ module Tree
|
|
890
905
|
# @see #previous_sibling
|
891
906
|
# @see #siblings
|
892
907
|
def next_sibling
|
893
|
-
return nil if
|
908
|
+
return nil if root?
|
894
909
|
|
895
|
-
|
896
|
-
parent.children.at(
|
910
|
+
idx = parent.children.index(self)
|
911
|
+
parent.children.at(idx + 1) if idx
|
897
912
|
end
|
898
913
|
|
899
914
|
# Previous sibling of this node.
|
@@ -907,17 +922,17 @@ module Tree
|
|
907
922
|
# @see #next_sibling
|
908
923
|
# @see #siblings
|
909
924
|
def previous_sibling
|
910
|
-
return nil if
|
925
|
+
return nil if root?
|
911
926
|
|
912
|
-
|
913
|
-
parent.children.at(
|
927
|
+
idx = parent.children.index(self)
|
928
|
+
parent.children.at(idx - 1) if idx&.positive?
|
914
929
|
end
|
915
930
|
|
916
931
|
# @!endgroup
|
917
932
|
|
918
|
-
# Provides a
|
933
|
+
# Provides a comparison operation for the nodes.
|
919
934
|
#
|
920
|
-
#
|
935
|
+
# Comparison is based on the natural ordering of the node name objects.
|
921
936
|
#
|
922
937
|
# @param [Tree::TreeNode] other The other node to compare against.
|
923
938
|
#
|
@@ -925,8 +940,9 @@ module Tree
|
|
925
940
|
# this node is a 'predecessor'. Returns 'nil' if the other
|
926
941
|
# object is not a 'Tree::TreeNode'.
|
927
942
|
def <=>(other)
|
928
|
-
return nil if other
|
929
|
-
|
943
|
+
return nil if other.nil? || !other.is_a?(Tree::TreeNode)
|
944
|
+
|
945
|
+
name <=> other.name
|
930
946
|
end
|
931
947
|
|
932
948
|
# Pretty prints the (sub)tree rooted at this node.
|
@@ -937,17 +953,18 @@ module Tree
|
|
937
953
|
# @param [Proc] block optional block to use for rendering
|
938
954
|
def print_tree(level = node_depth, max_depth = nil,
|
939
955
|
block = lambda { |node, prefix|
|
940
|
-
|
941
|
-
|
956
|
+
puts "#{prefix} #{node.name}"
|
957
|
+
})
|
958
|
+
prefix = ''.dup # dup NEEDs to be invoked to make this mutable.
|
942
959
|
|
943
|
-
if
|
960
|
+
if root?
|
944
961
|
prefix << '*'
|
945
962
|
else
|
946
|
-
prefix << '|' unless parent.
|
963
|
+
prefix << '|' unless parent.last_sibling?
|
947
964
|
prefix << (' ' * (level - 1) * 4)
|
948
|
-
prefix << (
|
965
|
+
prefix << (last_sibling? ? '+' : '|')
|
949
966
|
prefix << '---'
|
950
|
-
prefix << (
|
967
|
+
prefix << (children? ? '+' : '>')
|
951
968
|
end
|
952
969
|
|
953
970
|
block.call(self, prefix)
|
@@ -955,10 +972,10 @@ module Tree
|
|
955
972
|
# Exit if the max level is defined, and reached.
|
956
973
|
return unless max_depth.nil? || level < max_depth
|
957
974
|
|
958
|
-
|
959
|
-
|
960
|
-
|
975
|
+
# Child might be 'nil'
|
976
|
+
children do |child|
|
977
|
+
child&.print_tree(level + 1, max_depth, block)
|
978
|
+
end
|
961
979
|
end
|
962
|
-
|
963
980
|
end
|
964
981
|
end
|