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

evolve75 / RubyTree / 299

11 Aug 2017 - 14:08 coverage: 98.293% (-0.2%) from 98.526%
299

Pull #65

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
do not swap false for nil in #detached_copy
Pull Request #65: Unclonable content

3 of 4 new or added lines in 1 file covered. (75.0%)

403 of 410 relevant lines covered (98.29%)

2694.88 hits per line

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

96.97
/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 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
  class TreeNode
1×
87
    include Enumerable
1×
88
    include Comparable
1×
89
    include Tree::Utils::TreeMetricsHandler
1×
90
    include Tree::Utils::TreePathHandler
1×
91
    include Tree::Utils::CamelCaseMethodHandler
1×
92
    include Tree::Utils::JSONConverter
1×
93
    include Tree::Utils::TreeMergeHandler
1×
94
    include Tree::Utils::HashConverter
1×
95

96
    # @!group Core Attributes
97

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

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

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

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

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

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

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

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

178
      parentage_array = []
2×
179
      prev_parent = self.parent
2×
180
      while (prev_parent)
7×
181
        parentage_array << prev_parent
3×
182
        prev_parent = prev_parent.parent
3×
183
      end
184
      parentage_array
2×
185
    end
186

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

197
    # @!group Node Creation
198

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

221
      if name.kind_of?(Integer)
2,770×
222
        warn StandardWarning,
6×
223
             "Using integer as node name."\
224
             " Semantics of TreeNode[] may not be what you expect!"\
225
             " #{name} #{content}"
226
      end
227

228
      self.set_as_root!
2,770×
229
      @children_hash = Hash.new
2,770×
230
      @children = []
2,770×
231
    end
232

233
    # Returns a copy of this node, with its parent and children links removed.
234
    # The original node remains attached to its tree.
235
    #
236
    # @return [Tree::TreeNode] A copy of this node.
237
    def detached_copy
1×
238
      cloned_content =
64×
239
        begin
240
          @content && @content.clone
64×
241
        rescue TypeError
NEW
242
          @content
!
243
        end
244
      self.class.new(@name, cloned_content)
64×
245
    end
246

247
    # Returns a copy of entire (sub-)tree from this node.
248
    #
249
    # @author Vincenzo Farruggia
250
    # @since 0.8.0
251
    #
252
    # @return [Tree::TreeNode] A copy of (sub-)tree from this node.
253
    def detached_subtree_copy
1×
254
      new_node = detached_copy
24×
255
      children { |child| new_node << child.detached_subtree_copy }
39×
256
      new_node
24×
257
    end
258

259
    # Alias for {Tree::TreeNode#detached_subtree_copy}
260
    #
261
    # @see Tree::TreeNode#detached_subtree_copy
262
    alias :dup :detached_subtree_copy
1×
263

264
    # Returns a {marshal-dump}[http://ruby-doc.org/core-1.8.7/Marshal.html]
265
    # represention of the (sub)tree rooted at this node.
266
    #
267
    def marshal_dump
1×
268
      self.collect { |node| node.create_dump_rep }
8×
269
    end
270

271
    # Creates a dump representation of this node and returns the same as
272
    # a hash.
273
    def create_dump_rep           # :nodoc:
1×
274
      { :name => @name,
275
        :parent => (is_root? ? nil : @parent.name),
6×
276
        :content => Marshal.dump(@content)
277
      }
6×
278
    end
279

280
    protected :create_dump_rep
1×
281

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

298
        if parent_name then
6×
299
          nodes[name] = current_node = Tree::TreeNode.new(name, content)
4×
300
          nodes[parent_name].add current_node
4×
301
        else
302
          # This is the root node, hence initialize self.
303
          initialize(name, content)
2×
304

305
          nodes[name] = self    # Add self to the list of nodes
2×
306
        end
307
      end
308
    end
309

310
    # @!endgroup
311

312
    # Returns string representation of this node.
313
    # This method is primarily meant for debugging purposes.
314
    #
315
    # @return [String] A string representation of the node.
316
    def to_s
1×
317
      "Node Name: #{@name}" +
318
        " Content: " + (@content.to_s || "<Empty>") +
5×
319
        " Parent: " + (is_root?()  ? "<None>" : @parent.name.to_s) +
5×
320
        " Children: #{@children.length}" +
321
        " Total Nodes: #{size()}"
5×
322
    end
323

324
    # @!group Structure Modification
325

326
    # Convenience synonym for {Tree::TreeNode#add} method.
327
    #
328
    # This method allows an easy mechanism to add node hierarchies to the tree
329
    # on a given path via chaining the method calls to successive child nodes.
330
    #
331
    # @example Add a child and grand-child to the root
332
    #   root << child << grand_child
333
    #
334
    # @param [Tree::TreeNode] child the child node to add.
335
    #
336
    # @return [Tree::TreeNode] The added child node.
337
    #
338
    # @see Tree::TreeNode#add
339
    def <<(child)
1×
340
      add(child)
2,384×
341
    end
342

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

396
      # Lazy mans unique test, won't test if children of child are unique in
397
      # this tree too.
398
      raise "Child #{child.name} already added!"\
399
            if @children_hash.include?(child.name)
2,398×
400

401
      child.parent.remove! child if child.parent # Detach from the old parent
2,395×
402

403
      if insertion_range.include?(at_index)
2,395×
404
        @children.insert(at_index, child)
2,393×
405
      else
406
        raise "Attempting to insert a child at a non-existent location"\
2×
407
              " (#{at_index}) "\
408
              "when only positions from "\
409
              "#{insertion_range.min} to #{insertion_range.max} exist."
410
      end
411

412
      @children_hash[child.name]  = child
2,393×
413
      child.parent = self
2,393×
414
      return child
2,393×
415
    end
416

417
    # Return a range of valid insertion positions.  Used in the #add method.
418
    def insertion_range
1×
419
      max = @children.size
2,399×
420
      min = -(max+1)
2,399×
421
      min..max
2,399×
422
    end
423

424
    private :insertion_range
1×
425

426
    # Renames the node and updates the parent's reference to it
427
    #
428
    # @param [Object] new_name Name of the node. Conventional usage is to pass a
429
    #                          String (Integer names may cause *surprises*)
430
    #
431
    # @return [Object] The old name
432
    def rename(new_name)
1×
433
      old_name = @name
2×
434

435
      if is_root?
2×
436
        self.name=(new_name)
1×
437
      else
438
        @parent.rename_child old_name, new_name
1×
439
      end
440

441
      old_name
2×
442
    end
443

444
    # Renames the specified child node
445
    #
446
    # @param [Object] old_name old Name of the node. Conventional usage is to
447
    #                     pass a String (Integer names may cause *surprises*)
448
    #
449
    # @param [Object] new_name new Name of the node. Conventional usage is to
450
    #   pass a String (Integer names may cause *surprises*)
451
    def rename_child(old_name, new_name)
1×
452
      raise ArgumentError, "Invalid child name specified: #{old_name}"\
453
            unless @children_hash.has_key?(old_name)
3×
454

455
      @children_hash[new_name] = @children_hash.delete(old_name)
2×
456
      @children_hash[new_name].name=(new_name)
2×
457
    end
458

459
    # Protected method to set the name of this node.
460
    # This method should *NOT* be invoked by client code.
461
    #
462
    # @param [Object] new_name The node Name to set.
463
    #
464
    # @return [Object] The new name.
465
    def name=(new_name)
1×
466
      @name = new_name
3×
467
    end
468

469
    # Replaces the specified child node with another child node on this node.
470
    #
471
    # @param [Tree::TreeNode] old_child The child node to be replaced.
472
    # @param [Tree::TreeNode] new_child The child node to be replaced with.
473
    #
474
    # @return [Tree::TreeNode] The removed child node
475
    def replace!(old_child, new_child)
1×
476
      child_index = @children.find_index(old_child)
3×
477

478
      old_child = remove! old_child
3×
479
      add new_child, child_index
3×
480

481
      return old_child
3×
482
    end
483

484
    # Replaces the node with another node
485
    #
486
    # @param [Tree::TreeNode] node The node to replace this node with
487
    #
488
    # @return [Tree::TreeNode] The replaced child node
489
    def replace_with(node)
1×
490
      @parent.replace!(self, node)
1×
491
    end
492

493
    # Removes the specified child node from this node.
494
    #
495
    # This method can also be used for *pruning* a sub-tree, in cases where the removed child node is
496
    # the root of the sub-tree to be pruned.
497
    #
498
    # The removed child node is orphaned but accessible if an alternate reference exists.  If accessible via
499
    # an alternate reference, the removed child will report itself as a root node for its sub-tree.
500
    #
501
    # @param [Tree::TreeNode] child The child node to remove.
502
    #
503
    # @return [Tree::TreeNode] The removed child node, or +nil+ if a +nil+ was passed in as argument.
504
    #
505
    # @see #remove_from_parent!
506
    # @see #remove_all!
507
    def remove!(child)
1×
508
      return nil unless child
41×
509

510
      @children_hash.delete(child.name)
40×
511
      @children.delete(child)
40×
512
      child.set_as_root!
40×
513
      child
40×
514
    end
515

516
    # Protected method to set the parent node for this node.
517
    # This method should *NOT* be invoked by client code.
518
    #
519
    # @param [Tree::TreeNode] parent The parent node.
520
    #
521
    # @return [Tree::TreeNode] The parent node.
522
    def parent=(parent)         # :nodoc:
1×
523
      @parent = parent
5,221×
524
      @node_depth = nil
5,221×
525
    end
526

527
    protected :parent=, :name=
1×
528

529
    # Removes this node from its parent. This node becomes the new root for its
530
    # subtree.
531
    #
532
    # If this is the root node, then does nothing.
533
    #
534
    # @return [Tree:TreeNode] +self+ (the removed node) if the operation is
535
    #                                successful, +nil+ otherwise.
536
    #
537
    # @see #remove_all!
538
    def remove_from_parent!
1×
539
      @parent.remove!(self) unless is_root?
6×
540
    end
541

542
    # Removes all children from this node. If an independent reference exists to
543
    # the child nodes, then these child nodes report themselves as roots after
544
    # this operation.
545
    #
546
    # @return [Tree::TreeNode] this node (+self+)
547
    #
548
    # @see #remove!
549
    # @see #remove_from_parent!
550
    def remove_all!
1×
551
      @children.each { |child| child.set_as_root! }
15×
552

553
      @children_hash.clear
4×
554
      @children.clear
4×
555
      self
4×
556
    end
557

558
    # Protected method which sets this node as a root node.
559
    #
560
    # @return +nil+.
561
    def set_as_root!              # :nodoc:
1×
562
      self.parent = nil
2,821×
563
    end
564

565
    protected :set_as_root!
1×
566

567
    # Freezes all nodes in the (sub)tree rooted at this node.
568
    #
569
    # The nodes become immutable after this operation.  In effect, the entire tree's
570
    # structure and contents become _read-only_ and cannot be changed.
571
    def freeze_tree!
1×
572
      each {|node| node.freeze}
8×
573
    end
574

575
    # @!endgroup
576

577
    # @!group Tree Traversal
578

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

616
      if name_or_index.kind_of?(Integer) and not num_as_name
203×
617
        @children[name_or_index]
77×
618
      else
619
        if num_as_name and not name_or_index.kind_of?(Integer)
126×
620
          warn StandardWarning,
2×
621
             "Redundant use of the `num_as_name` flag for non-integer node name"
622
        end
623
        @children_hash[name_or_index]
126×
624
      end
625
    end
626

627
    # Traverses each node (including this node) of the (sub)tree rooted at this
628
    # node by yielding the nodes to the specified block.
629
    #
630
    # The traversal is *depth-first* and from *left-to-right* in pre-ordered
631
    # sequence.
632
    #
633
    # @yieldparam node [Tree::TreeNode] Each node.
634
    #
635
    # @see #preordered_each
636
    # @see #breadth_each
637
    #
638
    # @return [Tree::TreeNode] this node, if a block if given
639
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
640
    def each(&block)             # :yields: node
1×
641

642
     return self.to_enum unless block_given?
57×
643

644
      node_stack = [self]   # Start with this node
56×
645

646
      until node_stack.empty?
56×
647
        current = node_stack.shift    # Pop the top-most node
4,184×
648
        if current                    # Might be 'nil' (esp. for binary trees)
4,184×
649
          yield current               # and process it
4,184×
650
          # Stack children of the current node at top of the stack
651
          node_stack = current.children.concat(node_stack)
4,180×
652
        end
653
      end
654

655
      return self if block_given?
52×
656
    end
657

658
    # Traverses the (sub)tree rooted at this node in pre-ordered sequence.
659
    # This is a synonym of {Tree::TreeNode#each}.
660
    #
661
    # @yieldparam node [Tree::TreeNode] Each node.
662
    #
663
    # @see #each
664
    # @see #breadth_each
665
    #
666
    # @return [Tree::TreeNode] this node, if a block if given
667
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
668
    def preordered_each(&block)  # :yields: node
1×
669
      each(&block)
2×
670
    end
671

672
    # Traverses the (sub)tree rooted at this node in post-ordered sequence.
673
    #
674
    # @yieldparam node [Tree::TreeNode] Each node.
675
    #
676
    # @see #preordered_each
677
    # @see #breadth_each
678
    # @return [Tree::TreeNode] this node, if a block if given
679
    # @return [Enumerator] an enumerator on this tree, if a block is *not* given
680
    def postordered_each(&block)
1×
681
      return self.to_enum(:postordered_each) unless block_given?
4×
682

683
      # Using a marked node in order to skip adding the children of nodes that
684
      # have already been visited. This allows the stack depth to be controlled,
685
      # and also allows stateful backtracking.
686
      markednode = Struct.new(:node, :visited)
2×
687
      node_stack = [markednode.new(self, false)] # Start with self
2×
688

689
      until node_stack.empty?
2×
690
        peek_node = node_stack[0]
22×
691
        if peek_node.node.has_children? and not peek_node.visited
22×
692
          peek_node.visited = true
8×
693
          # Add the children to the stack. Use the marking structure.
694
          marked_children =
8×
695
            peek_node.node.children.map {|node| markednode.new(node, false)}
12×
696
          node_stack = marked_children.concat(node_stack)
8×
697
          next
8×
698
        else
699
          yield node_stack.shift.node           # Pop and yield the current node
14×
700
        end
701
      end
702

703
      return self if block_given?
2×
704
    end
705

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

720
      node_queue = [self]       # Create a queue with self as the initial entry
2×
721

722
      # Use a queue to do breadth traversal
723
      until node_queue.empty?
2×
724
        node_to_traverse = node_queue.shift
14×
725
        yield node_to_traverse
14×
726
        # Enqueue the children from left to right.
727
        node_to_traverse.children { |child| node_queue.push child }
26×
728
      end
729

730
      return self if block_given?
2×
731
    end
732

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

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

776
    # @!endgroup
777

778
    # @!group Navigating the Child Nodes
779

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

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

796
    # @!group Navigating the Sibling Nodes
797

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

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

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

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

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

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

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

901
      myidx = parent.children.index(self)
4×
902
      parent.children.at(myidx + 1) if myidx
4×
903
    end
904

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

918
      myidx = parent.children.index(self)
4×
919
      parent.children.at(myidx - 1) if myidx && myidx > 0
4×
920
    end
921

922
    # @!endgroup
923

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

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

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

959
      block.call(self, prefix)
1×
960

961
      # Exit if the max level is defined, and reached.
962
      return unless max_depth.nil? || level < max_depth
1×
963

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

969
  end
970
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