Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolve75 / RubyTree / 304

21 Dec 2017 - 18:04 coverage: 4.198% (-94.3%) from 98.526%
304

Pull #68

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
cleanup_and_gemfile_update Updated the gem dependencies.

Also, updated the usage of `structured_warnings` gem.
Pull Request #68: Cleanup and gemfile update. Releasing 0.9.7

3 of 44 new or added lines in 3 files covered. (6.82%)

204 existing lines in 2 files now uncovered.

11 of 262 relevant lines covered (4.2%)

0.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.65
/lib/tree.rb
1
# tree.rb - This file is part of the RubyTree package.
2
#
3
# = tree.rb - Generic implementation of an N-ary tree data structure.
4
#
5
# Provides a generic tree data structure with ability to
6
# store keyed node elements in the tree.  This implementation
7
# mixes in the Enumerable module.
8
#
9
# Author:: Anupam Sengupta (anupamsg@gmail.com)
10
#
11

12
# Copyright (c) 2006-2015, 2017 Anupam Sengupta
13
#
14
# All rights reserved.
15
#
16
# Redistribution and use in source and binary forms, with or without
17
# modification, are permitted provided that the following conditions are met:
18
#
19
# - Redistributions of source code must retain the above copyright notice, this
20
#   list of conditions and the following disclaimer.
21
#
22
# - Redistributions in binary form must reproduce the above copyright notice,
23
#   this list of conditions and the following disclaimer in the documentation
24
#   and/or other materials provided with the distribution.
25
#
26
# - Neither the name of the organization nor the names of its contributors may
27
#   be used to endorse or promote products derived from this software without
28
#   specific prior written permission.
29
#
30
#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
34
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
38
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
#
41

42
require 'tree/tree_deps'
1×
43

44
# This module provides a *TreeNode* class whose instances are the primary
45
# objects for representing nodes in the tree.
46
#
47
# This module also acts as the namespace for all classes in the *RubyTree*
48
# package.
49
module Tree
1×
50

51
  # == TreeNode Class Description
52
  #
53
  # This class models the nodes for an *N-ary* tree data structure. The
54
  # nodes are *named*, and have a place-holder for the node data (i.e.,
55
  # _content_ of the node). The node names are required to be *unique*
56
  # amongst the sibling/peer nodes. Note that the name is implicitly
57
  # used as an _ID_ within the data structure).
58
  #
59
  # The node's _content_ is *not* required to be unique across
60
  # different nodes in the tree, and can be +nil+ as well.
61
  #
62
  # The class provides various methods to navigate the tree, traverse
63
  # the structure, modify contents of the node, change position of the
64
  # node in the tree, and to make structural changes to the tree.
65
  #
66
  # A node can have any number of *child* nodes attached to it and
67
  # hence can be used to create N-ary trees.  Access to the child
68
  # nodes can be made in order (with the conventional left to right
69
  # access), or randomly.
70
  #
71
  # The node also provides direct access to its *parent* node as well
72
  # as other superior parents in the path to root of the tree.  In
73
  # addition, a node can also access its *sibling* nodes, if present.
74
  #
75
  # Note that while this implementation does not _explicitly_ support
76
  # directed graphs, the class itself makes no restrictions on
77
  # associating a node's *content* with multiple nodes in the tree.
78
  # However, having duplicate nodes within the structure is likely to
79
  # cause unpredictable behavior.
80
  #
81
  # == Example
82
  #
83
  # {include:file:examples/example_basic.rb}
84
  #
85
  # @author Anupam Sengupta
86
  # noinspection RubyTooManyMethodsInspection
87
  class TreeNode
1×
88
    include Enumerable
1×
89
    include Comparable
1×
90
    include Tree::Utils::TreeMetricsHandler
1×
UNCOV
91
    include Tree::Utils::TreePathHandler
!
UNCOV
92
    include Tree::Utils::CamelCaseMethodHandler
!
UNCOV
93
    include Tree::Utils::JSONConverter
!
UNCOV
94
    include Tree::Utils::TreeMergeHandler
!
UNCOV
95
    include Tree::Utils::HashConverter
!
96

97
    # @!group Core Attributes
98

99
    # @!attribute [r] name
100
    #
101
    # Name of this node.  Expected to be unique within the tree.
102
    #
103
    # Note that the name attribute really functions as an *ID* within
104
    # the tree structure, and hence the uniqueness constraint is
105
    # required.
106
    #
107
    # This may be changed in the future, but for now it is best to
108
    # retain unique names within the tree structure, and use the
109
    # +content+ attribute for any non-unique node requirements.
110
    #
111
    # If you want to change the name, you probably want to call +rename+
112
    # instead.
113
    #
114
    # @see content
115
    # @see rename
UNCOV
116
    attr_reader   :name
!
117

118
    # @!attribute [rw] content
119
    # Content of this node.  Can be +nil+.  Note that there is no
120
    # uniqueness constraint related to this attribute.
121
    #
122
    # @see name
UNCOV
123
    attr_accessor :content
!
124

125
    # @!attribute [r] parent
126
    # Parent of this node.  Will be +nil+ for a root node.
UNCOV
127
    attr_reader   :parent
!
128

129
    # @!attribute [r] root
130
    # Root node for the (sub)tree to which this node belongs.
131
    # A root node's root is itself.
132
    #
133
    # @return [Tree::TreeNode] Root of the (sub)tree.
UNCOV
134
    def root
!
UNCOV
135
      root = self
!
NEW
136
      root = root.parent until root.is_root?
!
UNCOV
137
      root
!
138
    end
139

140
    # @!attribute [r] is_root?
141
    # Returns +true+ if this is a root node.  Note that
142
    # orphaned children will also be reported as root nodes.
143
    #
144
    # @return [Boolean] +true+ if this is a root node.
UNCOV
145
    def is_root?
!
UNCOV
146
      @parent.nil?
!
147
    end
148

149
    # @!attribute [r] has_content?
150
    # +true+ if this node has content.
151
    #
152
    # @return [Boolean] +true+ if the node has content.
UNCOV
153
    def has_content?
!
UNCOV
154
      @content != nil
!
155
    end
156

157
    # @!attribute [r] is_leaf?
158
    # +true+ if this node is a _leaf_ - i.e., one without
159
    # any children.
160
    #
161
    # @return [Boolean] +true+ if this is a leaf node.
162
    #
163
    # @see #has_children?
UNCOV
164
    def is_leaf?
!
UNCOV
165
      !has_children?
!
166
    end
167

168
    # @!attribute [r] parentage
169
    # An array of ancestors of this node in reversed order
170
    # (the first element is the immediate parent of this node).
171
    #
172
    # Returns +nil+ if this is a root node.
173
    #
174
    # @return [Array<Tree::TreeNode>] An array of ancestors of this node
175
    # @return [nil] if this is a root node.
UNCOV
176
    def parentage
!
UNCOV
177
      return nil if is_root?
!
178

UNCOV
179
      parentage_array = []
!
UNCOV
180
      prev_parent = self.parent
!
NEW
181
      while prev_parent
!
UNCOV
182
        parentage_array << prev_parent
!
UNCOV
183
        prev_parent = prev_parent.parent
!
184
      end
UNCOV
185
      parentage_array
!
186
    end
187

188
    # @!attribute [r] has_children?
189
    # +true+ if the this node has any child node.
190
    #
191
    # @return [Boolean] +true+ if child nodes exist.
192
    #
193
    # @see #is_leaf?
UNCOV
194
    def has_children?
!
UNCOV
195
      @children.length != 0
!
196
    end
197

198
    # @!group Node Creation
199

200
    # Creates a new node with a name and optional content.
201
    # The node name is expected to be unique within the tree.
202
    #
203
    # The content can be of any type, and defaults to +nil+.
204
    #
205
    # @param [Object] name Name of the node. Conventional usage is to pass a
206
    #   String (Integer names may cause *surprises*)
207
    #
208
    # @param [Object] content Content of the node.
209
    #
210
    # @raise [ArgumentError] Raised if the node name is empty.
211
    #
212
    # @note If the name is an +Integer+, then the semantics of {#[]} access
213
    #   method can be surprising, as an +Integer+ parameter to that method
214
    #   normally acts as an index to the children array, and follows the
215
    #   _zero-based_ indexing convention.
216
    #
217
    # @see #[]
UNCOV
218
    def initialize(name, content = nil)
!
NEW
219
      raise ArgumentError, 'Node name HAS to be provided!' if name == nil
!
UNCOV
220
      @name, @content = name, content
!
221

UNCOV
222
      if name.kind_of?(Integer)
!
NEW
223
        warn StructuredWarnings::StandardWarning,
!
224
             'Using integer as node name.'\
225
             ' Semantics of TreeNode[] may not be what you expect!'\
226
             " #{name} #{content}"
227
      end
228

UNCOV
229
      self.set_as_root!
!
UNCOV
230
      @children_hash = Hash.new
!
UNCOV
231
      @children = []
!
232
    end
233

234
    # Returns a copy of this node, with its parent and children links removed.
235
    # The original node remains attached to its tree.
236
    #
237
    # @return [Tree::TreeNode] A copy of this node.
UNCOV
238
    def detached_copy
!
UNCOV
239
      self.class.new(@name, @content ? @content.clone : nil)
!
240
    end
241

242
    # Returns a copy of entire (sub-)tree from this node.
243
    #
244
    # @author Vincenzo Farruggia
245
    # @since 0.8.0
246
    #
247
    # @return [Tree::TreeNode] A copy of (sub-)tree from this node.
UNCOV
248
    def detached_subtree_copy
!
UNCOV
249
      new_node = detached_copy
!
UNCOV
250
      children { |child| new_node << child.detached_subtree_copy }
!
UNCOV
251
      new_node
!
252
    end
253

254
    # Alias for {Tree::TreeNode#detached_subtree_copy}
255
    #
256
    # @see Tree::TreeNode#detached_subtree_copy
UNCOV
257
    alias :dup :detached_subtree_copy
!
258

259
    # Returns a {marshal-dump}[http://ruby-doc.org/core-1.8.7/Marshal.html]
260
    # representation of the (sub)tree rooted at this node.
261
    #
UNCOV
262
    def marshal_dump
!
UNCOV
263
      self.collect { |node| node.create_dump_rep }
!
264
    end
265

266
    # Creates a dump representation of this node and returns the same as
267
    # a hash.
UNCOV
268
    def create_dump_rep           # :nodoc:
!
269
      {name: @name,
NEW
270
       parent: (is_root? ? nil : @parent.name),
!
271
       content: Marshal.dump(@content)
UNCOV
272
      }
!
273
    end
274

UNCOV
275
    protected :create_dump_rep
!
276

277
    # Loads a marshaled dump of a tree and returns the root node of the
278
    # reconstructed tree. See the
279
    # {Marshal}[http://ruby-doc.org/core-1.8.7/Marshal.html] class for
280
    # additional details.
281
    #
282
    #
283
    # @todo This method probably should be a class method. It currently clobbers
284
    #       self and makes itself the root.
285
    #
UNCOV
286
    def marshal_load(dumped_tree_array)
!
UNCOV
287
      nodes = { }
!
UNCOV
288
      dumped_tree_array.each do |node_hash|
!
UNCOV
289
        name        = node_hash[:name]
!
UNCOV
290
        parent_name = node_hash[:parent]
!
UNCOV
291
        content     = Marshal.load(node_hash[:content])
!
292

NEW
293
        if parent_name
!
UNCOV
294
          nodes[name] = current_node = Tree::TreeNode.new(name, content)
!
UNCOV
295
          nodes[parent_name].add current_node
!
296
        else
297
          # This is the root node, hence initialize self.
UNCOV
298
          initialize(name, content)
!
299

UNCOV
300
          nodes[name] = self    # Add self to the list of nodes
!
301
        end
302
      end
303
    end
304

305
    # @!endgroup
306

307
    # Returns string representation of this node.
308
    # This method is primarily meant for debugging purposes.
309
    #
310
    # @return [String] A string representation of the node.
UNCOV
311
    def to_s
!
NEW
312
      "Node Name: #{@name} Content: #{(@content.to_s || '<Empty>')} Parent: #{(is_root? ? '<None>' : @parent.name.to_s)} Children: #{@children.length} Total Nodes: #{size}"
!
313
    end
314

315
    # @!group Structure Modification
316

317
    # Convenience synonym for {Tree::TreeNode#add} method.
318
    #
319
    # This method allows an easy mechanism to add node hierarchies to the tree
320
    # on a given path via chaining the method calls to successive child nodes.
321
    #
322
    # @example Add a child and grand-child to the root
323
    #   root << child << grand_child
324
    #
325
    # @param [Tree::TreeNode] child the child node to add.
326
    #
327
    # @return [Tree::TreeNode] The added child node.
328
    #
329
    # @see Tree::TreeNode#add
UNCOV
330
    def <<(child)
!
UNCOV
331
      add(child)
!
332
    end
333

334
    # Adds the specified child node to this node.
335
    #
336
    # This method can also be used for *grafting* a subtree into this
337
    # node's tree, if the specified child node is the root of a subtree (i.e.,
338
    # has child nodes under it).
339
    #
340
    # this node becomes parent of the node passed in as the argument, and
341
    # the child is added as the last child ("right most") in the current set of
342
    # children of this node.
343
    #
344
    # Additionally you can specify a insert position. The new node will be
345
    # inserted BEFORE that position. If you don't specify any position the node
346
    # will be just appended. This feature is provided to make implementation of
347
    # node movement within the tree very simple.
348
    #
349
    # If an insertion position is provided, it needs to be within the valid
350
    # range of:
351
    #
352
    #    -children.size..children.size
353
    #
354
    # This is to prevent +nil+ nodes being created as children if a non-existent
355
    # position is used.
356
    #
357
    # If the new node being added has an existing parent node, then it will be
358
    # removed from this pre-existing parent prior to being added as a child to
359
    # this node. I.e., the child node will effectively be moved from its old
360
    # parent to this node. In this situation, after the operation is complete,
361
    # the node will no longer exist as a child for its old parent.
362
    #
363
    # @param [Tree::TreeNode] child The child node to add.
364
    #
365
    # @param [optional, Number] at_index The optional position where the node is
366
    #                                    to be inserted.
367
    #
368
    # @return [Tree::TreeNode] The added child node.
369
    #
370
    # @raise [RuntimeError] This exception is raised if another child node with
371
    #                       the same name exists, or if an invalid insertion
372
    #                       position is specified.
373
    #
374
    # @raise [ArgumentError] This exception is raised if a +nil+ node is passed
375
    #                        as the argument.
376
    #
377
    # @see #<<
UNCOV
378
    def add(child, at_index = -1)
!
379
      # Only handles the immediate child scenario
380
      raise ArgumentError,
NEW
381
            'Attempting to add a nil node' unless child
!
382
      raise ArgumentError,
NEW
383
            'Attempting add node to itself' if self.equal?(child)
!
384
      raise ArgumentError,
NEW
385
            'Attempting add root as a child' if child.equal?(root)
!
386

387
      # Lazy mans unique test, won't test if children of child are unique in
388
      # this tree too.
389
      raise "Child #{child.name} already added!"\
UNCOV
390
            if @children_hash.include?(child.name)
!
391

UNCOV
392
      child.parent.remove! child if child.parent # Detach from the old parent
!
393

UNCOV
394
      if insertion_range.include?(at_index)
!
UNCOV
395
        @children.insert(at_index, child)
!
396
      else
NEW
397
        raise 'Attempting to insert a child at a non-existent location'\
!
398
              " (#{at_index}) "\
399
              'when only positions from '\
400
              "#{insertion_range.min} to #{insertion_range.max} exist."
401
      end
402

UNCOV
403
      @children_hash[child.name]  = child
!
UNCOV
404
      child.parent = self
!
NEW
405
      child
!
406
    end
407

408
    # Return a range of valid insertion positions.  Used in the #add method.
UNCOV
409
    def insertion_range
!
UNCOV
410
      max = @children.size
!
UNCOV
411
      min = -(max+1)
!
UNCOV
412
      min..max
!
413
    end
414

UNCOV
415
    private :insertion_range
!
416

417
    # Renames the node and updates the parent's reference to it
418
    #
419
    # @param [Object] new_name Name of the node. Conventional usage is to pass a
420
    #                          String (Integer names may cause *surprises*)
421
    #
422
    # @return [Object] The old name
UNCOV
423
    def rename(new_name)
!
UNCOV
424
      old_name = @name
!
425

UNCOV
426
      if is_root?
!
UNCOV
427
        self.name=(new_name)
!
428
      else
UNCOV
429
        @parent.rename_child old_name, new_name
!
430
      end
431

UNCOV
432
      old_name
!
433
    end
434

435
    # Renames the specified child node
436
    #
437
    # @param [Object] old_name old Name of the node. Conventional usage is to
438
    #                     pass a String (Integer names may cause *surprises*)
439
    #
440
    # @param [Object] new_name new Name of the node. Conventional usage is to
441
    #   pass a String (Integer names may cause *surprises*)
UNCOV
442
    def rename_child(old_name, new_name)
!
443
      raise ArgumentError, "Invalid child name specified: #{old_name}"\
UNCOV
444
            unless @children_hash.has_key?(old_name)
!
445

UNCOV
446
      @children_hash[new_name] = @children_hash.delete(old_name)
!
UNCOV
447
      @children_hash[new_name].name=(new_name)
!
448
    end
449

450
    # Protected method to set the name of this node.
451
    # This method should *NOT* be invoked by client code.
452
    #
453
    # @param [Object] new_name The node Name to set.
454
    #
455
    # @return [Object] The new name.
UNCOV
456
    def name=(new_name)
!
UNCOV
457
      @name = new_name
!
458
    end
459

460
    # Replaces the specified child node with another child node on this node.
461
    #
462
    # @param [Tree::TreeNode] old_child The child node to be replaced.
463
    # @param [Tree::TreeNode] new_child The child node to be replaced with.
464
    #
465
    # @return [Tree::TreeNode] The removed child node
UNCOV
466
    def replace!(old_child, new_child)
!
UNCOV
467
      child_index = @children.find_index(old_child)
!
468

UNCOV
469
      old_child = remove! old_child
!
UNCOV
470
      add new_child, child_index
!
471

NEW
472
      old_child
!
473
    end
474

475
    # Replaces the node with another node
476
    #
477
    # @param [Tree::TreeNode] node The node to replace this node with
478
    #
479
    # @return [Tree::TreeNode] The replaced child node
UNCOV
480
    def replace_with(node)
!
UNCOV
481
      @parent.replace!(self, node)
!
482
    end
483

484
    # Removes the specified child node from this node.
485
    #
486
    # This method can also be used for *pruning* a sub-tree, in cases where the removed child node is
487
    # the root of the sub-tree to be pruned.
488
    #
489
    # The removed child node is orphaned but accessible if an alternate reference exists.  If accessible via
490
    # an alternate reference, the removed child will report itself as a root node for its sub-tree.
491
    #
492
    # @param [Tree::TreeNode] child The child node to remove.
493
    #
494
    # @return [Tree::TreeNode] The removed child node, or +nil+ if a +nil+ was passed in as argument.
495
    #
496
    # @see #remove_from_parent!
497
    # @see #remove_all!
UNCOV
498
    def remove!(child)
!
UNCOV
499
      return nil unless child
!
500

UNCOV
501
      @children_hash.delete(child.name)
!
UNCOV
502
      @children.delete(child)
!
UNCOV
503
      child.set_as_root!
!
UNCOV
504
      child
!
505
    end
506

507
    # Protected method to set the parent node for this node.
508
    # This method should *NOT* be invoked by client code.
509
    #
510
    # @param [Tree::TreeNode] parent The parent node.
511
    #
512
    # @return [Tree::TreeNode] The parent node.
UNCOV
513
    def parent=(parent)         # :nodoc:
!
UNCOV
514
      @parent = parent
!
UNCOV
515
      @node_depth = nil
!
516
    end
517

UNCOV
518
    protected :parent=, :name=
!
519

520
    # Removes this node from its parent. This node becomes the new root for its
521
    # subtree.
522
    #
523
    # If this is the root node, then does nothing.
524
    #
525
    # @return [Tree:TreeNode] +self+ (the removed node) if the operation is
526
    #                                successful, +nil+ otherwise.
527
    #
528
    # @see #remove_all!
UNCOV
529
    def remove_from_parent!
!
UNCOV
530
      @parent.remove!(self) unless is_root?
!
531
    end
532

533
    # Removes all children from this node. If an independent reference exists to
534
    # the child nodes, then these child nodes report themselves as roots after
535
    # this operation.
536
    #
537
    # @return [Tree::TreeNode] this node (+self+)
538
    #
539
    # @see #remove!
540
    # @see #remove_from_parent!
UNCOV
541
    def remove_all!
!
UNCOV
542
      @children.each { |child| child.set_as_root! }
!
543

UNCOV
544
      @children_hash.clear
!
UNCOV
545
      @children.clear
!
UNCOV
546
      self
!
547
    end
548

549
    # Protected method which sets this node as a root node.
550
    #
551
    # @return +nil+.
UNCOV
552
    def set_as_root!              # :nodoc:
!
UNCOV
553
      self.parent = nil
!
554
    end
555

UNCOV
556
    protected :set_as_root!
!
557

558
    # Freezes all nodes in the (sub)tree rooted at this node.
559
    #
560
    # The nodes become immutable after this operation.  In effect, the entire tree's
561
    # structure and contents become _read-only_ and cannot be changed.
UNCOV
562
    def freeze_tree!
!
UNCOV
563
      each {|node| node.freeze}
!
564
    end
565

566
    # @!endgroup
567

568
    # @!group Tree Traversal
569

570
    # Returns the requested node from the set of immediate children.
571
    #
572
    # - If the +name+ argument is an _Integer_, then the in-sequence
573
    #   array of children is accessed using the argument as the
574
    #   *index* (zero-based).  However, if the second _optional_
575
    #   +num_as_name+ argument is +true+, then the +name+ is used
576
    #   literally as a name, and *NOT* as an *index*
577
    #
578
    # - If the +name+ argument is *NOT* an _Integer_, then it is taken to
579
    #   be the *name* of the child node to be returned.
580
    #
581
    # If a non-+Integer+ +name+ is passed, and the +num_as_name+
582
    # parameter is also +true+, then a warning is thrown (as this is a
583
    # redundant use of the +num_as_name+ flag.)
584
    #
585
    # @param [String|Number] name_or_index Name of the child, or its
586
    #   positional index in the array of child nodes.
587
    #
588
    # @param [Boolean] num_as_name Whether to treat the +Integer+
589
    #   +name+ argument as an actual name, and *NOT* as an _index_ to
590
    #   the children array.
591
    #
592
    # @return [Tree::TreeNode] the requested child node.  If the index
593
    #   in not in range, or the name is not present, then a +nil+
594
    #   is returned.
595
    #
596
    # @note The use of +Integer+ names is allowed by using the optional
597
    #       +num_as_name+ flag.
598
    #
599
    # @raise [ArgumentError] Raised if the +name_or_index+ argument is +nil+.
600
    #
601
    # @see #add
602
    # @see #initialize
UNCOV
603
    def [](name_or_index, num_as_name=false)
!
604
      raise ArgumentError,
NEW
605
            'Name_or_index needs to be provided!' if name_or_index == nil
!
606

UNCOV
607
      if name_or_index.kind_of?(Integer) and not num_as_name
!
UNCOV
608
        @children[name_or_index]
!
609
      else
UNCOV
610
        if num_as_name and not name_or_index.kind_of?(Integer)
!
NEW
611
          warn StructuredWarnings::StandardWarning,
!
612
               'Redundant use of the `num_as_name` flag for non-integer node name'
613
        end
UNCOV
614
        @children_hash[name_or_index]
!
615
      end
616
    end
617

618
    # Traverses each node (including this node) of the (sub)tree rooted at this
619
    # node by yielding the nodes to the specified block.
620
    #
621
    # The traversal is *depth-first* and from *left-to-right* in pre-ordered
622
    # sequence.
623
    #
624
    # @param [Object] block
625
    # @yieldparam node [Tree::TreeNode] Each node.
626
    #
627
    # @see #preordered_each
628
    # @see #breadth_each
629
    #
630
    # @return [Tree::TreeNode] this node, if a block if given
631
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
632
    # noinspection RubyUnusedLocalVariable
UNCOV
633
    def each(&block)             # :yields: node
!
634

UNCOV
635
     return self.to_enum unless block_given?
!
636

UNCOV
637
      node_stack = [self]   # Start with this node
!
638

UNCOV
639
      until node_stack.empty?
!
UNCOV
640
        current = node_stack.shift    # Pop the top-most node
!
UNCOV
641
        if current                    # Might be 'nil' (esp. for binary trees)
!
UNCOV
642
          yield current               # and process it
!
643
          # Stack children of the current node at top of the stack
UNCOV
644
          node_stack = current.children.concat(node_stack)
!
645
        end
646
      end
647

NEW
648
      self if block_given?
!
649
    end
650

651
    # Traverses the (sub)tree rooted at this node in pre-ordered sequence.
652
    # This is a synonym of {Tree::TreeNode#each}.
653
    #
654
    # @yieldparam node [Tree::TreeNode] Each node.
655
    #
656
    # @see #each
657
    # @see #breadth_each
658
    #
659
    # @return [Tree::TreeNode] this node, if a block if given
660
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
UNCOV
661
    def preordered_each(&block)  # :yields: node
!
UNCOV
662
      each(&block)
!
663
    end
664

665
    # Traverses the (sub)tree rooted at this node in post-ordered sequence.
666
    #
667
    # @param [Object] block
668
    # @yieldparam node [Tree::TreeNode] Each node.
669
    #
670
    # @see #preordered_each
671
    # @see #breadth_each
672
    # @return [Tree::TreeNode] this node, if a block if given
673
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
674
    # noinspection RubyUnusedLocalVariable
UNCOV
675
    def postordered_each(&block)
!
UNCOV
676
      return self.to_enum(:postordered_each) unless block_given?
!
677

678
      # Using a marked node in order to skip adding the children of nodes that
679
      # have already been visited. This allows the stack depth to be controlled,
680
      # and also allows stateful backtracking.
NEW
681
      marked_node = Struct.new(:node, :visited)
!
NEW
682
      node_stack = [marked_node.new(self, false)] # Start with self
!
683

UNCOV
684
      until node_stack.empty?
!
UNCOV
685
        peek_node = node_stack[0]
!
UNCOV
686
        if peek_node.node.has_children? and not peek_node.visited
!
UNCOV
687
          peek_node.visited = true
!
688
          # Add the children to the stack. Use the marking structure.
UNCOV
689
          marked_children =
!
NEW
690
            peek_node.node.children.map {|node| marked_node.new(node, false)}
!
UNCOV
691
          node_stack = marked_children.concat(node_stack)
!
UNCOV
692
          next
!
693
        else
UNCOV
694
          yield node_stack.shift.node           # Pop and yield the current node
!
695
        end
696
      end
697

NEW
698
      self if block_given?
!
699
    end
700

701
    # Performs breadth-first traversal of the (sub)tree rooted at this node. The
702
    # traversal at a given level is from *left-to-right*. this node itself is
703
    # the first node to be traversed.
704
    #
705
    # @param [Object] block
706
    # @yieldparam node [Tree::TreeNode] Each node.
707
    #
708
    # @see #preordered_each
709
    # @see #breadth_each
710
    #
711
    # @return [Tree::TreeNode] this node, if a block if given
712
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
713
    # noinspection RubyUnusedLocalVariable
UNCOV
714
    def breadth_each(&block)
!
UNCOV
715
      return self.to_enum(:breadth_each) unless block_given?
!
716

UNCOV
717
      node_queue = [self]       # Create a queue with self as the initial entry
!
718

719
      # Use a queue to do breadth traversal
UNCOV
720
      until node_queue.empty?
!
UNCOV
721
        node_to_traverse = node_queue.shift
!
UNCOV
722
        yield node_to_traverse
!
723
        # Enqueue the children from left to right.
UNCOV
724
        node_to_traverse.children { |child| node_queue.push child }
!
725
      end
726

NEW
727
      self if block_given?
!
728
    end
729

730
    # An array of all the immediate children of this node. The child
731
    # nodes are ordered "left-to-right" in the returned array.
732
    #
733
    # If a block is given, yields each child node to the block
734
    # traversing from left to right.
735
    #
736
    # @yieldparam child [Tree::TreeNode] Each child node.
737
    #
738
    # @return [Tree::TreeNode] This node, if a block is given
739
    #
740
    # @return [Array<Tree::TreeNode>] An array of the child nodes, if no block
741
    #                                 is given.
UNCOV
742
    def children
!
UNCOV
743
      if block_given?
!
UNCOV
744
        @children.each {|child| yield child}
!
NEW
745
        self
!
746
      else
NEW
747
        @children.clone
!
748
      end
749
    end
750

751
    # Yields every leaf node of the (sub)tree rooted at this node to the
752
    # specified block.
753
    #
754
    # May yield this node as well if this is a leaf node.
755
    # Leaf traversal is *depth-first* and *left-to-right*.
756
    #
757
    # @param [Object] block
758
    # @yieldparam node [Tree::TreeNode] Each leaf node.
759
    #
760
    # @see #each
761
    # @see #breadth_each
762
    #
763
    # @return [Tree::TreeNode] this node, if a block if given
764
    # @return [Array<Tree::TreeNode>] An array of the leaf nodes
765
    # noinspection RubyUnusedLocalVariable
NEW
766
    def each_leaf(&block)
!
UNCOV
767
      if block_given?
!
UNCOV
768
        self.each { |node| yield(node) if node.is_leaf? }
!
NEW
769
        self
!
770
      else
NEW
771
        self.select { |node| node.is_leaf? }
!
772
      end
773
    end
774

775
    # @!endgroup
776

777
    # @!group Navigating the Child Nodes
778

779
    # First child of this node.
780
    # Will be +nil+ if no children are present.
781
    #
782
    # @return [Tree::TreeNode] The first child, or +nil+ if none is present.
UNCOV
783
    def first_child
!
NEW
784
      @children.first
!
785
    end
786

787
    # Last child of this node.
788
    # Will be +nil+ if no children are present.
789
    #
790
    # @return [Tree::TreeNode] The last child, or +nil+ if none is present.
UNCOV
791
    def last_child
!
NEW
792
      @children.last
!
793
    end
794

795
    # @!group Navigating the Sibling Nodes
796

797
    # First sibling of this node. If this is the root node, then returns
798
    # itself.
799
    #
800
    # 'First' sibling is defined as follows:
801
    #
802
    # First sibling:: The left-most child of this node's parent, which may be
803
    # this node itself
804
    #
805
    # @return [Tree::TreeNode] The first sibling node.
806
    #
807
    # @see #is_first_sibling?
808
    # @see #last_sibling
UNCOV
809
    def first_sibling
!
UNCOV
810
      is_root? ? self : parent.children.first
!
811
    end
812

813
    # Returns +true+ if this node is the first sibling at its level.
814
    #
815
    # @return [Boolean] +true+ if this is the first sibling.
816
    #
817
    # @see #is_last_sibling?
818
    # @see #first_sibling
UNCOV
819
    def is_first_sibling?
!
UNCOV
820
      first_sibling == self
!
821
    end
822

823
    # Last sibling of this node.  If this is the root node, then returns
824
    # itself.
825
    #
826
    # 'Last' sibling is defined as follows:
827
    #
828
    # Last sibling:: The right-most child of this node's parent, which may be
829
    # this node itself
830
    #
831
    # @return [Tree::TreeNode] The last sibling node.
832
    #
833
    # @see #is_last_sibling?
834
    # @see #first_sibling
UNCOV
835
    def last_sibling
!
UNCOV
836
      is_root? ? self : parent.children.last
!
837
    end
838

839
    # Returns +true+ if this node is the last sibling at its level.
840
    #
841
    # @return [Boolean] +true+ if this is the last sibling.
842
    #
843
    # @see #is_first_sibling?
844
    # @see #last_sibling
UNCOV
845
    def is_last_sibling?
!
UNCOV
846
      last_sibling == self
!
847
    end
848

849
    # An array of siblings for this node. This node is excluded.
850
    #
851
    # If a block is provided, yields each of the sibling nodes to the block.
852
    # The root always has +nil+ siblings.
853
    #
854
    # @yieldparam sibling [Tree::TreeNode] Each sibling node.
855
    #
856
    # @return [Array<Tree::TreeNode>] Array of siblings of this node. Will
857
    #                                 return an empty array for *root*
858
    #
859
    # @return [Tree::TreeNode] This node, if no block is given
860
    #
861
    # @see #first_sibling
862
    # @see #last_sibling
UNCOV
863
    def siblings
!
UNCOV
864
      if block_given?
!
UNCOV
865
        parent.children.each { |sibling| yield sibling if sibling != self }
!
NEW
866
        self
!
867
      else
UNCOV
868
        return [] if is_root?
!
UNCOV
869
        siblings = []
!
UNCOV
870
        parent.children {|my_sibling|
!
UNCOV
871
                         siblings << my_sibling if my_sibling != self}
!
UNCOV
872
        siblings
!
873
      end
874
    end
875

876
    # +true+ if this node is the only child of its parent.
877
    #
878
    # As a special case, a root node will always return +true+.
879
    #
880
    # @return [Boolean] +true+ if this is the only child of its parent.
881
    #
882
    # @see #siblings
UNCOV
883
    def is_only_child?
!
UNCOV
884
      is_root? ? true : parent.children.size == 1
!
885
    end
886

887
    # Next sibling for this node.
888
    # The _next_ node is defined as the node to right of this node.
889
    #
890
    # Will return +nil+ if no subsequent node is present, or if this is a root
891
    # node.
892
    #
893
    # @return [Tree::treeNode] the next sibling node, if present.
894
    #
895
    # @see #previous_sibling
896
    # @see #siblings
UNCOV
897
    def next_sibling
!
UNCOV
898
      return nil if is_root?
!
899

NEW
900
      idx = parent.children.index(self)
!
NEW
901
      parent.children.at(idx + 1) if idx
!
902
    end
903

904
    # Previous sibling of this node.
905
    # _Previous_ node is defined to be the node to left of this node.
906
    #
907
    # Will return +nil+ if no predecessor node is present, or if this is a root
908
    # node.
909
    #
910
    # @return [Tree::treeNode] the previous sibling node, if present.
911
    #
912
    # @see #next_sibling
913
    # @see #siblings
UNCOV
914
    def previous_sibling
!
UNCOV
915
      return nil if is_root?
!
916

NEW
917
      idx = parent.children.index(self)
!
NEW
918
      parent.children.at(idx - 1) if idx && idx > 0
!
919
    end
920

921
    # @!endgroup
922

923
    # Provides a comparison operation for the nodes.
924
    #
925
    # Comparison is based on the natural ordering of the node name objects.
926
    #
927
    # @param [Tree::TreeNode] other The other node to compare against.
928
    #
929
    # @return [Integer] +1 if this node is a 'successor', 0 if equal and -1 if
930
    #                   this node is a 'predecessor'. Returns 'nil' if the other
931
    #                   object is not a 'Tree::TreeNode'.
UNCOV
932
    def <=>(other)
!
UNCOV
933
      return nil if other == nil || other.class != Tree::TreeNode
!
UNCOV
934
      self.name <=> other.name
!
935
    end
936

937
    # Pretty prints the (sub)tree rooted at this node.
938
    #
939
    # @param [Integer] level The indentation level (4 spaces) to start with.
940
    # @param [Integer] max_depth optional maximum depth at which the printing
941
    #                            with stop.
942
    # @param [Proc] block optional block to use for rendering
NEW
943
    def print_tree(level = self.node_depth, max_depth = nil,
!
944
                   block = lambda { |node, prefix|
UNCOV
945
                     puts "#{prefix} #{node.name}" })
!
UNCOV
946
      prefix = ''
!
947

UNCOV
948
      if is_root?
!
UNCOV
949
        prefix << '*'
!
950
      else
951
        prefix << '|' unless parent.is_last_sibling?
!
952
        prefix << (' ' * (level - 1) * 4)
!
953
        prefix << (is_last_sibling? ? '+' : '|')
!
954
        prefix << '---'
!
955
        prefix << (has_children? ? '+' : '>')
!
956
      end
957

UNCOV
958
      block.call(self, prefix)
!
959

960
      # Exit if the max level is defined, and reached.
UNCOV
961
      return unless max_depth.nil? || level < max_depth
!
962

UNCOV
963
      children { |child|
!
964
        child.print_tree(level + 1,
965
                         max_depth, block) if child } # Child might be 'nil'
!
966
    end
967

968
  end
969
end
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc