kanbanworkflowstimelinescrumrubyroadmapproject-planningproject-managementopenprojectangularissue-trackerifcgantt-chartganttbug-trackerboardsbcf
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
540 lines
16 KiB
540 lines
16 KiB
15 years ago
|
# 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.
|
||
|
#
|