Chris@0: # tree.rb Chris@0: # Chris@0: # $Revision: 1.29 $ by $Author: anupamsg $ Chris@0: # $Name: $ Chris@0: # Chris@0: # = tree.rb - Generic Multi-way Tree implementation Chris@0: # Chris@0: # Provides a generic tree data structure with ability to Chris@0: # store keyed node elements in the tree. The implementation Chris@0: # mixes in the Enumerable module. Chris@0: # Chris@0: # Author:: Anupam Sengupta (anupamsg@gmail.com) Chris@0: # Chris@0: Chris@0: # Copyright (c) 2006, 2007 Anupam Sengupta Chris@0: # Chris@0: # All rights reserved. Chris@0: # Chris@0: # Redistribution and use in source and binary forms, with or without modification, Chris@0: # are permitted provided that the following conditions are met: Chris@0: # Chris@0: # - Redistributions of source code must retain the above copyright notice, this Chris@0: # list of conditions and the following disclaimer. Chris@0: # Chris@0: # - Redistributions in binary form must reproduce the above copyright notice, this Chris@0: # list of conditions and the following disclaimer in the documentation and/or Chris@0: # other materials provided with the distribution. Chris@0: # Chris@0: # - Neither the name of the organization nor the names of its contributors may Chris@0: # be used to endorse or promote products derived from this software without Chris@0: # specific prior written permission. Chris@0: # Chris@0: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" Chris@0: # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE Chris@0: # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE Chris@0: # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR Chris@0: # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES Chris@0: # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; Chris@0: # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON Chris@0: # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT Chris@0: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS Chris@0: # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Chris@0: # Chris@0: Chris@0: # This module provides a TreeNode class which is the primary class for all Chris@0: # nodes represented in the Tree. Chris@0: # This module mixes in the Enumerable module. Chris@0: module Tree Chris@0: Chris@0: # Rubytree Package Version Chris@0: VERSION = '0.5.2' Chris@0: Chris@0: # == TreeNode Class Description Chris@0: # Chris@0: # The node class for the tree representation. the nodes are named and have a Chris@0: # place-holder for the node data (i.e., the `content' of the node). The node Chris@0: # names are expected to be unique. In addition, the node provides navigation Chris@0: # methods to traverse the tree. Chris@0: # Chris@0: # The nodes can have any number of child nodes attached to it. Note that while Chris@0: # this implementation does not support directed graphs, the class itself makes Chris@0: # no restrictions on associating a node's CONTENT with multiple parent nodes. Chris@0: # Chris@0: # Chris@0: # == Example Chris@0: # Chris@0: # The following code-snippet implements this tree structure: Chris@0: # Chris@0: # +------------+ Chris@0: # | ROOT | Chris@0: # +-----+------+ Chris@0: # +-------------+------------+ Chris@0: # | | Chris@0: # +-------+-------+ +-------+-------+ Chris@0: # | CHILD 1 | | CHILD 2 | Chris@0: # +-------+-------+ +---------------+ Chris@0: # | Chris@0: # | Chris@0: # +-------+-------+ Chris@0: # | GRANDCHILD 1 | Chris@0: # +---------------+ Chris@0: # Chris@0: # require 'tree' Chris@0: # Chris@0: # myTreeRoot = Tree::TreeNode.new("ROOT", "Root Content") Chris@0: # Chris@0: # myTreeRoot << Tree::TreeNode.new("CHILD1", "Child1 Content") << Tree::TreeNode.new("GRANDCHILD1", "GrandChild1 Content") Chris@0: # Chris@0: # myTreeRoot << Tree::TreeNode.new("CHILD2", "Child2 Content") Chris@0: # Chris@0: # myTreeRoot.printTree Chris@0: # Chris@0: # child1 = myTreeRoot["CHILD1"] Chris@0: # Chris@0: # grandChild1 = myTreeRoot["CHILD1"]["GRANDCHILD1"] Chris@0: # Chris@0: # siblingsOfChild1Array = child1.siblings Chris@0: # Chris@0: # immediateChildrenArray = myTreeRoot.children Chris@0: # Chris@0: # # Process all nodes Chris@0: # Chris@0: # myTreeRoot.each { |node| node.content.reverse } Chris@0: # Chris@0: # myTreeRoot.remove!(child1) # Remove the child Chris@0: class TreeNode Chris@0: include Enumerable Chris@0: Chris@0: attr_reader :content, :name, :parent Chris@0: attr_writer :content Chris@0: Chris@0: # Constructor which expects the name of the node Chris@0: # Chris@0: # Name of the node is expected to be unique across the Chris@0: # tree. Chris@0: # Chris@0: # The content can be of any type, and is defaulted to _nil_. Chris@0: def initialize(name, content = nil) Chris@0: raise "Node name HAS to be provided" if name == nil Chris@0: @name = name Chris@0: @content = content Chris@0: self.setAsRoot! Chris@0: Chris@0: @childrenHash = Hash.new Chris@0: @children = [] Chris@0: end Chris@0: Chris@0: # Returns a copy of this node, with the parent and children links removed. Chris@0: def detached_copy Chris@0: Tree::TreeNode.new(@name, @content ? @content.clone : nil) Chris@0: end Chris@0: Chris@0: # Print the string representation of this node. Chris@0: def to_s Chris@0: "Node Name: #{@name}" + Chris@0: " Content: " + (@content || "") + Chris@0: " Parent: " + (isRoot?() ? "" : @parent.name) + Chris@0: " Children: #{@children.length}" + Chris@0: " Total Nodes: #{size()}" Chris@0: end Chris@0: Chris@0: # Returns an array of ancestors in reversed order (the first element is the Chris@0: # immediate parent). Returns nil if this is a root node. Chris@0: def parentage Chris@0: return nil if isRoot? Chris@0: Chris@0: parentageArray = [] Chris@0: prevParent = self.parent Chris@0: while (prevParent) Chris@0: parentageArray << prevParent Chris@0: prevParent = prevParent.parent Chris@0: end Chris@0: Chris@0: parentageArray Chris@0: end Chris@0: Chris@0: # Protected method to set the parent node. Chris@0: # This method should NOT be invoked by client code. Chris@0: def parent=(parent) Chris@0: @parent = parent Chris@0: end Chris@0: Chris@0: # Convenience synonym for TreeNode#add method. This method allows a convenient Chris@0: # method to add children hierarchies in the tree. Chris@0: # Chris@0: # E.g. root << child << grand_child Chris@0: def <<(child) Chris@0: add(child) Chris@0: end Chris@0: Chris@0: # Adds the specified child node to the receiver node. The child node's Chris@0: # parent is set to be the receiver. The child is added as the last child in Chris@0: # the current list of children for the receiver node. Chris@0: def add(child) Chris@0: raise "Child already added" if @childrenHash.has_key?(child.name) Chris@0: Chris@0: @childrenHash[child.name] = child Chris@0: @children << child Chris@0: child.parent = self Chris@0: return child Chris@0: Chris@0: end Chris@0: Chris@0: # Removes the specified child node from the receiver node. The removed Chris@0: # children nodes are orphaned but available if an alternate reference Chris@0: # exists. Chris@0: # Chris@0: # Returns the child node. Chris@0: def remove!(child) Chris@0: @childrenHash.delete(child.name) Chris@0: @children.delete(child) Chris@0: child.setAsRoot! unless child == nil Chris@0: return child Chris@0: end Chris@0: Chris@0: # Removes this node from its parent. If this is the root node, then does Chris@0: # nothing. Chris@0: def removeFromParent! Chris@0: @parent.remove!(self) unless isRoot? Chris@0: end Chris@0: Chris@0: # Removes all children from the receiver node. Chris@0: def removeAll! Chris@0: for child in @children Chris@0: child.setAsRoot! Chris@0: end Chris@0: @childrenHash.clear Chris@0: @children.clear Chris@0: self Chris@0: end Chris@0: Chris@0: # Indicates whether this node has any associated content. Chris@0: def hasContent? Chris@0: @content != nil Chris@0: end Chris@0: Chris@0: # Protected method which sets this node as a root node. Chris@0: def setAsRoot! Chris@0: @parent = nil Chris@0: end Chris@0: Chris@0: # Indicates whether this node is a root node. Note that Chris@0: # orphaned children will also be reported as root nodes. Chris@0: def isRoot? Chris@0: @parent == nil Chris@0: end Chris@0: Chris@0: # Indicates whether this node has any immediate child nodes. Chris@0: def hasChildren? Chris@0: @children.length != 0 Chris@0: end Chris@0: Chris@0: # Indicates whether this node is a 'leaf' - i.e., one without Chris@0: # any children Chris@0: def isLeaf? Chris@0: !hasChildren? Chris@0: end Chris@0: Chris@0: # Returns an array of all the immediate children. If a block is given, Chris@0: # yields each child node to the block. Chris@0: def children Chris@0: if block_given? Chris@0: @children.each {|child| yield child} Chris@0: else Chris@0: @children Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns the first child of this node. Will return nil if no children are Chris@0: # present. Chris@0: def firstChild Chris@0: children.first Chris@0: end Chris@0: Chris@0: # Returns the last child of this node. Will return nil if no children are Chris@0: # present. Chris@0: def lastChild Chris@0: children.last Chris@0: end Chris@0: Chris@0: # Returns every node (including the receiver node) from the tree to the Chris@0: # specified block. The traversal is depth first and from left to right in Chris@0: # pre-ordered sequence. Chris@0: def each &block Chris@0: yield self Chris@0: children { |child| child.each(&block) } Chris@0: end Chris@0: Chris@0: # Traverses the tree in a pre-ordered sequence. This is equivalent to Chris@0: # TreeNode#each Chris@0: def preordered_each &block Chris@0: each(&block) Chris@0: end Chris@0: Chris@0: # Performs breadth first traversal of the tree rooted at this node. The Chris@0: # traversal in a given level is from left to right. Chris@0: def breadth_each &block Chris@0: node_queue = [self] # Create a queue with self as the initial entry Chris@0: Chris@0: # Use a queue to do breadth traversal Chris@0: until node_queue.empty? Chris@0: node_to_traverse = node_queue.shift Chris@0: yield node_to_traverse Chris@0: # Enqueue the children from left to right. Chris@0: node_to_traverse.children { |child| node_queue.push child } Chris@0: end Chris@0: end Chris@0: Chris@0: # Yields all leaf nodes from this node to the specified block. May yield Chris@0: # this node as well if this is a leaf node. Leaf traversal depth first and Chris@0: # left to right. Chris@0: def each_leaf &block Chris@0: self.each { |node| yield(node) if node.isLeaf? } Chris@0: end Chris@0: Chris@0: # Returns the requested node from the set of immediate children. Chris@0: # Chris@0: # If the parameter is _numeric_, then the in-sequence array of children is Chris@0: # accessed (see Tree#children). If the parameter is not _numeric_, then it Chris@0: # is assumed to be the *name* of the child node to be returned. Chris@0: def [](name_or_index) Chris@0: raise "Name_or_index needs to be provided" if name_or_index == nil Chris@0: Chris@0: if name_or_index.kind_of?(Integer) Chris@0: @children[name_or_index] Chris@0: else Chris@0: @childrenHash[name_or_index] Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns the total number of nodes in this tree, rooted at the receiver Chris@0: # node. Chris@0: def size Chris@0: @children.inject(1) {|sum, node| sum + node.size} Chris@0: end Chris@0: Chris@0: # Convenience synonym for Tree#size Chris@0: def length Chris@0: size() Chris@0: end Chris@0: Chris@0: # Pretty prints the tree starting with the receiver node. Chris@0: def printTree(level = 0) Chris@0: Chris@0: if isRoot? Chris@0: print "*" Chris@0: else Chris@0: print "|" unless parent.isLastSibling? Chris@0: print(' ' * (level - 1) * 4) Chris@0: print(isLastSibling? ? "+" : "|") Chris@0: print "---" Chris@0: print(hasChildren? ? "+" : ">") Chris@0: end Chris@0: Chris@0: puts " #{name}" Chris@0: Chris@0: children { |child| child.printTree(level + 1)} Chris@0: end Chris@0: Chris@0: # Returns the root for this tree. Root's root is itself. Chris@0: def root Chris@0: root = self Chris@0: root = root.parent while !root.isRoot? Chris@0: root Chris@0: end Chris@0: Chris@0: # Returns the first sibling for this node. If this is the root node, returns Chris@0: # itself. Chris@0: def firstSibling Chris@0: if isRoot? Chris@0: self Chris@0: else Chris@0: parent.children.first Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns true if this node is the first sibling. Chris@0: def isFirstSibling? Chris@0: firstSibling == self Chris@0: end Chris@0: Chris@0: # Returns the last sibling for this node. If this node is the root, returns Chris@0: # itself. Chris@0: def lastSibling Chris@0: if isRoot? Chris@0: self Chris@0: else Chris@0: parent.children.last Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns true if his node is the last sibling Chris@0: def isLastSibling? Chris@0: lastSibling == self Chris@0: end Chris@0: Chris@0: # Returns an array of siblings for this node. Chris@0: # If a block is provided, yields each of the sibling Chris@0: # nodes to the block. The root always has nil siblings. Chris@0: def siblings Chris@0: return nil if isRoot? Chris@0: if block_given? Chris@0: for sibling in parent.children Chris@0: yield sibling if sibling != self Chris@0: end Chris@0: else Chris@0: siblings = [] Chris@0: parent.children {|sibling| siblings << sibling if sibling != self} Chris@0: siblings Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns true if this node is the only child of its parent Chris@0: def isOnlyChild? Chris@0: parent.children.size == 1 Chris@0: end Chris@0: Chris@0: # Returns the next sibling for this node. Will return nil if no subsequent Chris@0: # node is present. Chris@0: def nextSibling Chris@0: if myidx = parent.children.index(self) Chris@0: parent.children.at(myidx + 1) Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns the previous sibling for this node. Will return nil if no Chris@0: # subsequent node is present. Chris@0: def previousSibling Chris@0: if myidx = parent.children.index(self) Chris@0: parent.children.at(myidx - 1) if myidx > 0 Chris@0: end Chris@0: end Chris@0: Chris@0: # Provides a comparision operation for the nodes. Comparision Chris@0: # is based on the natural character-set ordering for the Chris@0: # node names. Chris@0: def <=>(other) Chris@0: return +1 if other == nil Chris@0: self.name <=> other.name Chris@0: end Chris@0: Chris@0: # Freezes all nodes in the tree Chris@0: def freezeTree! Chris@0: each {|node| node.freeze} Chris@0: end Chris@0: Chris@0: # Creates the marshal-dump represention of the tree rooted at this node. Chris@0: def marshal_dump Chris@0: self.collect { |node| node.createDumpRep } Chris@0: end Chris@0: Chris@0: # Creates a dump representation and returns the same as a hash. Chris@0: def createDumpRep Chris@0: { :name => @name, :parent => (isRoot? ? nil : @parent.name), :content => Marshal.dump(@content)} Chris@0: end Chris@0: Chris@0: # Loads a marshalled dump of the tree and returns the root node of the Chris@0: # reconstructed tree. See the Marshal class for additional details. Chris@0: def marshal_load(dumped_tree_array) Chris@0: nodes = { } Chris@0: for node_hash in dumped_tree_array do Chris@0: name = node_hash[:name] Chris@0: parent_name = node_hash[:parent] Chris@0: content = Marshal.load(node_hash[:content]) Chris@0: Chris@0: if parent_name then Chris@0: nodes[name] = current_node = Tree::TreeNode.new(name, content) Chris@0: nodes[parent_name].add current_node Chris@0: else Chris@0: # This is the root node, hence initialize self. Chris@0: initialize(name, content) Chris@0: Chris@0: nodes[name] = self # Add self to the list of nodes Chris@0: end Chris@0: end Chris@0: end Chris@0: Chris@0: # Returns depth of the tree from this node. A single leaf node has a Chris@0: # depth of 1. Chris@0: def depth Chris@0: return 1 if isLeaf? Chris@0: 1 + @children.collect { |child| child.depth }.max Chris@0: end Chris@0: Chris@0: # Returns breadth of the tree at this node level. A single node has a Chris@0: # breadth of 1. Chris@0: def breadth Chris@0: return 1 if isRoot? Chris@0: parent.children.size Chris@0: end Chris@0: Chris@0: protected :parent=, :setAsRoot!, :createDumpRep Chris@0: Chris@0: end Chris@0: end Chris@0: Chris@0: # $Log: tree.rb,v $ Chris@0: # Revision 1.29 2007/12/22 00:28:59 anupamsg Chris@0: # Added more test cases, and enabled ZenTest compatibility. Chris@0: # Chris@0: # Revision 1.28 2007/12/20 03:19:33 anupamsg Chris@0: # * README (Module): Modified the install instructions from source. Chris@0: # (Module): Updated the minor version number. Chris@0: # Chris@0: # Revision 1.27 2007/12/20 03:00:03 anupamsg Chris@0: # Minor code changes. Removed self_initialize from the protected methods' list. Chris@0: # Chris@0: # Revision 1.26 2007/12/20 02:50:04 anupamsg Chris@0: # (Tree::TreeNode): Removed the spurious self_initialize from the protected list. Chris@0: # Chris@0: # Revision 1.25 2007/12/19 20:28:05 anupamsg Chris@0: # Removed the unnecesary self_initialize method. Chris@0: # Chris@0: # Revision 1.24 2007/12/19 06:39:17 anupamsg Chris@0: # Removed the unnecessary field and record separator constants. Also updated the Chris@0: # history.txt file. Chris@0: # Chris@0: # Revision 1.23 2007/12/19 06:25:00 anupamsg Chris@0: # (Tree::TreeNode): Minor fix to the comments. Also fixed the private/protected Chris@0: # scope issue with the createDumpRep method. Chris@0: # Chris@0: # Revision 1.22 2007/12/19 06:22:03 anupamsg Chris@0: # Updated the marshalling logic to correctly handle non-string content. This Chris@0: # should fix the bug # 15614 ("When dumping with an Object as the content, you get Chris@0: # a delimiter collision") Chris@0: # Chris@0: # Revision 1.21 2007/12/19 02:24:17 anupamsg Chris@0: # Updated the marshalling logic to handle non-string contents on the nodes. Chris@0: # Chris@0: # Revision 1.20 2007/10/10 08:42:57 anupamsg Chris@0: # Release 0.4.3 Chris@0: # Chris@0: # Revision 1.19 2007/08/31 01:16:27 anupamsg Chris@0: # Added breadth and pre-order traversals for the tree. Also added a method Chris@0: # to return the detached copy of a node from the tree. Chris@0: # Chris@0: # Revision 1.18 2007/07/21 05:14:44 anupamsg Chris@0: # Added a VERSION constant to the Tree module, Chris@0: # and using the same in the Rakefile. Chris@0: # Chris@0: # Revision 1.17 2007/07/21 03:24:25 anupamsg Chris@0: # Minor edits to parameter names. User visible functionality does not change. Chris@0: # Chris@0: # Revision 1.16 2007/07/18 23:38:55 anupamsg Chris@0: # Minor updates to tree.rb Chris@0: # Chris@0: # Revision 1.15 2007/07/18 22:11:50 anupamsg Chris@0: # Added depth and breadth methods for the TreeNode. Chris@0: # Chris@0: # Revision 1.14 2007/07/18 19:33:27 anupamsg Chris@0: # Added a new binary tree implementation. Chris@0: # Chris@0: # Revision 1.13 2007/07/18 07:17:34 anupamsg Chris@0: # Fixed a issue where TreeNode.ancestors was shadowing Module.ancestors. This method Chris@0: # has been renamed to TreeNode.parentage. Chris@0: # Chris@0: # Revision 1.12 2007/07/17 03:39:28 anupamsg Chris@0: # Moved the CVS Log keyword to end of the files. Chris@0: #