1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
|
# tree.rb
#
# $Revision: 1.29 $ by $Author: anupamsg $
# $Name: $
#
# = tree.rb - Generic Multi-way Tree implementation
#
# Provides a generic tree data structure with ability to
# store keyed node elements in the tree. The implementation
# mixes in the Enumerable module.
#
# Author:: Anupam Sengupta (anupamsg@gmail.com)
#
# Copyright (c) 2006, 2007 Anupam Sengupta
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# - Neither the name of the organization nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# This module provides a TreeNode class which is the primary class for all
# nodes represented in the Tree.
# This module mixes in the Enumerable module.
module Tree
# Rubytree Package Version
VERSION = '0.5.2'
# == TreeNode Class Description
#
# The node class for the tree representation. the nodes are named and have a
# place-holder for the node data (i.e., the `content' of the node). The node
# names are expected to be unique. In addition, the node provides navigation
# methods to traverse the tree.
#
# The nodes can have any number of child nodes attached to it. Note that while
# this implementation does not support directed graphs, the class itself makes
# no restrictions on associating a node's CONTENT with multiple parent nodes.
#
#
# == Example
#
# The following code-snippet implements this tree structure:
#
# +------------+
# | ROOT |
# +-----+------+
# +-------------+------------+
# | |
# +-------+-------+ +-------+-------+
# | CHILD 1 | | CHILD 2 |
# +-------+-------+ +---------------+
# |
# |
# +-------+-------+
# | GRANDCHILD 1 |
# +---------------+
#
# require 'tree'
#
# myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content")
#
# myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content")
#
# myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content")
#
# myTreeRoot.printTree
#
# child1 = myTreeRoot["CHILD1"]
#
# grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"]
#
# siblingsOfChild1Array = child1.siblings
#
# immediateChildrenArray = myTreeRoot.children
#
# # Process all nodes
#
# myTreeRoot.each { |node| node.content.reverse }
#
# myTreeRoot.remove!(child1) # Remove the child
class TreeNode
include Enumerable
attr_reader :content, :name, :parent
attr_writer :content
# Constructor which expects the name of the node
#
# Name of the node is expected to be unique across the
# tree.
#
# The content can be of any type, and is defaulted to _nil_.
def initialize(name, content = nil)
raise "Node name HAS to be provided" if name == nil
@name = name
@content = content
self.setAsRoot!
@childrenHash = Hash.new
@children = []
end
# Returns a copy of this node, with the parent and children links removed.
def detached_copy
Tree::TreeNode.new(@name, @content ? @content.clone : nil)
end
# Print the string representation of this node.
def to_s
"Node Name: #{@name}" +
" Content: " + (@content || "<Empty>") +
" Parent: " + (isRoot?() ? "<None>" : @parent.name) +
" Children: #{@children.length}" +
" Total Nodes: #{size()}"
end
# Returns an array of ancestors in reversed order (the first element is the
# immediate parent). Returns nil if this is a root node.
def parentage
return nil if isRoot?
parentageArray = []
prevParent = self.parent
while (prevParent)
parentageArray << prevParent
prevParent = prevParent.parent
end
parentageArray
end
# Protected method to set the parent node.
# This method should NOT be invoked by client code.
def parent=(parent)
@parent = parent
end
# Convenience synonym for TreeNode#add method. This method allows a convenient
# method to add children hierarchies in the tree.
#
# E.g. root << child << grand_child
def <<(child)
add(child)
end
# Adds the specified child node to the receiver node. The child node's
# parent is set to be the receiver. The child is added as the last child in
# the current list of children for the receiver node.
def add(child)
raise "Child already added" if @childrenHash.has_key?(child.name)
@childrenHash[child.name] = child
@children << child
child.parent = self
return child
end
# Removes the specified child node from the receiver node. The removed
# children nodes are orphaned but available if an alternate reference
# exists.
#
# Returns the child node.
def remove!(child)
@childrenHash.delete(child.name)
@children.delete(child)
child.setAsRoot! unless child == nil
return child
end
# Removes this node from its parent. If this is the root node, then does
# nothing.
def removeFromParent!
@parent.remove!(self) unless isRoot?
end
# Removes all children from the receiver node.
def removeAll!
for child in @children
child.setAsRoot!
end
@childrenHash.clear
@children.clear
self
end
# Indicates whether this node has any associated content.
def hasContent?
@content != nil
end
# Protected method which sets this node as a root node.
def setAsRoot!
@parent = nil
end
# Indicates whether this node is a root node. Note that
# orphaned children will also be reported as root nodes.
def isRoot?
@parent == nil
end
# Indicates whether this node has any immediate child nodes.
def hasChildren?
@children.length != 0
end
# Indicates whether this node is a 'leaf' - i.e., one without
# any children
def isLeaf?
!hasChildren?
end
# Returns an array of all the immediate children. If a block is given,
# yields each child node to the block.
def children
if block_given?
@children.each {|child| yield child}
else
@children
end
end
# Returns the first child of this node. Will return nil if no children are
# present.
def firstChild
children.first
end
# Returns the last child of this node. Will return nil if no children are
# present.
def lastChild
children.last
end
# Returns every node (including the receiver node) from the tree to the
# specified block. The traversal is depth first and from left to right in
# pre-ordered sequence.
def each &block
yield self
children { |child| child.each(&block) }
end
# Traverses the tree in a pre-ordered sequence. This is equivalent to
# TreeNode#each
def preordered_each &block
each(&block)
end
# Performs breadth first traversal of the tree rooted at this node. The
# traversal in a given level is from left to right.
def breadth_each &block
node_queue = [self] # Create a queue with self as the initial entry
# Use a queue to do breadth traversal
until node_queue.empty?
node_to_traverse = node_queue.shift
yield node_to_traverse
# Enqueue the children from left to right.
node_to_traverse.children { |child| node_queue.push child }
end
end
# Yields all leaf nodes from this node to the specified block. May yield
# this node as well if this is a leaf node. Leaf traversal depth first and
# left to right.
def each_leaf &block
self.each { |node| yield(node) if node.isLeaf? }
end
# Returns the requested node from the set of immediate children.
#
# If the parameter is _numeric_, then the in-sequence array of children is
# accessed (see Tree#children). If the parameter is not _numeric_, then it
# is assumed to be the *name* of the child node to be returned.
def [](name_or_index)
raise "Name_or_index needs to be provided" if name_or_index == nil
if name_or_index.kind_of?(Integer)
@children[name_or_index]
else
@childrenHash[name_or_index]
end
end
# Returns the total number of nodes in this tree, rooted at the receiver
# node.
def size
@children.inject(1) {|sum, node| sum + node.size}
end
# Convenience synonym for Tree#size
def length
size()
end
# Pretty prints the tree starting with the receiver node.
def printTree(level = 0)
if isRoot?
print "*"
else
print "|" unless parent.isLastSibling?
print(' ' * (level - 1) * 4)
print(isLastSibling? ? "+" : "|")
print "---"
print(hasChildren? ? "+" : ">")
end
puts " #{name}"
children { |child| child.printTree(level + 1)}
end
# Returns the root for this tree. Root's root is itself.
def root
root = self
root = root.parent while !root.isRoot?
root
end
# Returns the first sibling for this node. If this is the root node, returns
# itself.
def firstSibling
if isRoot?
self
else
parent.children.first
end
end
# Returns true if this node is the first sibling.
def isFirstSibling?
firstSibling == self
end
# Returns the last sibling for this node. If this node is the root, returns
# itself.
def lastSibling
if isRoot?
self
else
parent.children.last
end
end
# Returns true if his node is the last sibling
def isLastSibling?
lastSibling == self
end
# Returns an array of siblings for this node.
# If a block is provided, yields each of the sibling
# nodes to the block. The root always has nil siblings.
def siblings
return nil if isRoot?
if block_given?
for sibling in parent.children
yield sibling if sibling != self
end
else
siblings = []
parent.children {|sibling| siblings << sibling if sibling != self}
siblings
end
end
# Returns true if this node is the only child of its parent
def isOnlyChild?
parent.children.size == 1
end
# Returns the next sibling for this node. Will return nil if no subsequent
# node is present.
def nextSibling
if myidx = parent.children.index(self)
parent.children.at(myidx + 1)
end
end
# Returns the previous sibling for this node. Will return nil if no
# subsequent node is present.
def previousSibling
if myidx = parent.children.index(self)
parent.children.at(myidx - 1) if myidx > 0
end
end
# Provides a comparision operation for the nodes. Comparision
# is based on the natural character-set ordering for the
# node names.
def <=>(other)
return +1 if other == nil
self.name <=> other.name
end
# Freezes all nodes in the tree
def freezeTree!
each {|node| node.freeze}
end
# Creates the marshal-dump represention of the tree rooted at this node.
def marshal_dump
self.collect { |node| node.createDumpRep }
end
# Creates a dump representation and returns the same as a hash.
def createDumpRep
{ :name => @name, :parent => (isRoot? ? nil : @parent.name), :content => Marshal.dump(@content)}
end
# Loads a marshalled dump of the tree and returns the root node of the
# reconstructed tree. See the Marshal class for additional details.
def marshal_load(dumped_tree_array)
nodes = { }
for node_hash in dumped_tree_array do
name = node_hash[:name]
parent_name = node_hash[:parent]
content = Marshal.load(node_hash[:content])
if parent_name then
nodes[name] = current_node = Tree::TreeNode.new(name, content)
nodes[parent_name].add current_node
else
# This is the root node, hence initialize self.
initialize(name, content)
nodes[name] = self # Add self to the list of nodes
end
end
end
# Returns depth of the tree from this node. A single leaf node has a
# depth of 1.
def depth
return 1 if isLeaf?
1 + @children.collect { |child| child.depth }.max
end
# Returns breadth of the tree at this node level. A single node has a
# breadth of 1.
def breadth
return 1 if isRoot?
parent.children.size
end
protected :parent=, :setAsRoot!, :createDumpRep
end
end
# $Log: tree.rb,v $
# Revision 1.29 2007/12/22 00:28:59 anupamsg
# Added more test cases, and enabled ZenTest compatibility.
#
# Revision 1.28 2007/12/20 03:19:33 anupamsg
# * README (Module): Modified the install instructions from source.
# (Module): Updated the minor version number.
#
# Revision 1.27 2007/12/20 03:00:03 anupamsg
# Minor code changes. Removed self_initialize from the protected methods' list.
#
# Revision 1.26 2007/12/20 02:50:04 anupamsg
# (Tree::TreeNode): Removed the spurious self_initialize from the protected list.
#
# Revision 1.25 2007/12/19 20:28:05 anupamsg
# Removed the unnecesary self_initialize method.
#
# Revision 1.24 2007/12/19 06:39:17 anupamsg
# Removed the unnecessary field and record separator constants. Also updated the
# history.txt file.
#
# Revision 1.23 2007/12/19 06:25:00 anupamsg
# (Tree::TreeNode): Minor fix to the comments. Also fixed the private/protected
# scope issue with the createDumpRep method.
#
# Revision 1.22 2007/12/19 06:22:03 anupamsg
# Updated the marshalling logic to correctly handle non-string content. This
# should fix the bug # 15614 ("When dumping with an Object as the content, you get
# a delimiter collision")
#
# Revision 1.21 2007/12/19 02:24:17 anupamsg
# Updated the marshalling logic to handle non-string contents on the nodes.
#
# Revision 1.20 2007/10/10 08:42:57 anupamsg
# Release 0.4.3
#
# Revision 1.19 2007/08/31 01:16:27 anupamsg
# Added breadth and pre-order traversals for the tree. Also added a method
# to return the detached copy of a node from the tree.
#
# Revision 1.18 2007/07/21 05:14:44 anupamsg
# Added a VERSION constant to the Tree module,
# and using the same in the Rakefile.
#
# Revision 1.17 2007/07/21 03:24:25 anupamsg
# Minor edits to parameter names. User visible functionality does not change.
#
# Revision 1.16 2007/07/18 23:38:55 anupamsg
# Minor updates to tree.rb
#
# Revision 1.15 2007/07/18 22:11:50 anupamsg
# Added depth and breadth methods for the TreeNode.
#
# Revision 1.14 2007/07/18 19:33:27 anupamsg
# Added a new binary tree implementation.
#
# Revision 1.13 2007/07/18 07:17:34 anupamsg
# Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method
# has been renamed to TreeNode.parentage.
#
# Revision 1.12 2007/07/17 03:39:28 anupamsg
# Moved the CVS Log keyword to end of the files.
#
|