I wrote the following package (Tree) because I needed to make some enhancements to tcllib's struct::tree [L1 ] for a project I'm working on. This package contains two classes (Node,Tree) that are implemented using TclOO. One of the advantages of this implementation is that at Tcl 8.6 and later you can use this code directly instead of trying to figure out how to load tcllib or the pieces needed for the struct::tree command.
AK: What where the enhancements you needed ?
tjk: The primary enhancement I needed was to the serialize/deserialize commands. The new command pair allows you to serialize a subtree and then deserialize the stream on an arbitrary leaf node. I was also looking for a good excuse to learn TclOO and I felt that an implementation of a Tree using TclOO makes a lot more sense than its current implementation using namespaces. The documentation below provides more details on other changes I made to the API.
(SEE MORE COMMENTS BELOW)
Documentation for the Tree class is very similar to that for ::struct::tree since it is a reimplementation. However, the API to the Tree class is some what different because of API differences between TclOO and the namespace command. The API is also different because some enhance were added to the Tree class implementation. The following section itemizes each of the ::struct::tree commands and describes any changes in the equivalent Tree class method. The following information documents the mapping from Tree class methods to the ::struct::tree commands. In the text below ::struct::tree command is preceded by '>>'. The Tree class ================================== set treeName [Tree new] >> ::struct::tree ?treeName? ?=|:=|as|deserialize source? >> treeName option ?arg arg ...? >> treeName = sourcetree >> treeName --> desttree The returned value from 'Tree new' is an instance of the Tree class. The instance does not have a name and the new method doesn't accept any initialization options. The short hands for tree initialization for a serialization are not available. Use the serialize/deserialize methods directly to achieve the same result. Tree class and methods ================================== NOTE: The ::struct::tree implementation does not allow node names that contained whitespace or colons (":"), the Tree class implementation doesn't have this restriction. ${treeName} ancestors node >> treeName ancestors node ${treeName} children node >> treeName children ?-all? node ?filter cmdprefix? The '-all' option and 'filter' have been deprecated in the current implementation of ::struct::tree and are available through the descendants command. ${treeName} cut node >> treeName cut node ${treeName} delete node ?node ...? >> treeName delete node ?node ...? ${treeName} depth node >> treeName depth node ${treeName} descendants node ?-filter cmdprefix? >> treeName descendants node ?filter cmdprefix? The command interface to the descendants method is the same as its ::struct::tree counterpart but the interface to the 'cmdprefix' is different. When the cmdprefix is called by the Tree class method it is passed the following arguments on its command line. 1) the Tree instance that generated the call 2) the name of a descendant node 3) the Node instance associated with (2) These arguments can then be used in the filter procedure to perform introspection on the node being filtered or the tree that generated the filter call. See the description of the Node class (below) for a list of methods available for node introspection. ${treeName} deserialize node serialization ?-force? >> treeName deserialize serialization The deserialize command was changed to support leaf extension as well as tree replacement. The form of the serialization isn't compatible with the ::struct::tree (see serialize below). The node argument is the leaf node that will be extended by the serialization. An error will be thrown if node isn't a leaf node. The -force option causes node to be truncated to a leaf prior to the deserialize processing. To duplicate the old deserialize behavior, use the following command. ${treeName} deserialize [${treeName} rootname] ${serialization} -force The name of the root node in the serialization stream is replaced by the name of the leaf node in the deserialization command. ${treeName} destroy >> treeName destroy ${treeName} exists ?node ...node? >> treeName exists node The new form accepts more than one node and return the first node it finds that doesn't exist, else a blank string. ${treeName} index node >> treeName index node ${treeName} insert parent index ?child ?child ...?? >> treeName insert parent index ?child ?child ...?? ${treeName} isleaf node >> treeName isleaf node ${treeName} keys ?node? ?pattern? >> treeName keys node ?pattern? The new form will return all keys in the tree if but the node and pattern are missing. To filter the list of all keys, set the node to a blank string and define a pattern. ${treeName} key.append node key value >> treeName append node key value ${treeName} key.exists node key >> treeName keyexists node key ${treeName} key.get node key >> treeName get node key ${treeName} key.getall node ?pattern? >> treeName getall node ?pattern? ${treeName} key.lappend node key value >> treeName lappend node key value ${treeName} key.lappend node key value >> treeName leaves ${treeName} key.nodes ${treeName} key.nodes key -nodes list ${treeName} key.nodes key -glob globpattern ${treeName} key.nodes key -regexp repattern >> treeName attr key >> treeName attr key -nodes list >> treeName attr key -glob globpattern >> treeName attr key -regexp repattern ${treeName} key.set node key ?value? >> treeName set node key ?value? ${treeName} key.unset node key >> treeName unset node key ${treeName} move parent index node ?node ...? >> treeName move parent index node ?node ...? ${treeName} next node >> treeName next node ${treeName} numchildren node >> treeName numchildren node ${treeName} nodes >> treeName nodes ${treeName} parent node >> treeName parent node ${treeName} previous node >> treeName previous node ${treeName} rename node newname >> treeName rename node newname ${treeName} rootname >> treeName rootname ${treeName} serialize ?node? >> treeName serialize ?node? The serialize method doesn't produce a serialization that is compatible with the ::struct::tree serialize command. The result returned from the method is a three element list. The elements in the list are (1) a node name (2) an attribute dictionary for the node (3) a multiple of additional 3 element lists that recursively serialize the children of the node. The serialization directly encodes the tree structure so the first node name is the root of the tree. Since node names are only used once name referencing isn't required. ${treeName} size ?node? >> treeName size ?node? ${treeName} splice parent from ?to? ?child? >> treeName splice parent from ?to? ?child? ${treeName} swap node1 node2 >> treeName swap node1 node2 ${treeName} walkproc node cmdprefix ?-order order? ?-type type? >> treeName walkproc node ?-order order? ?-type type? cmdprefix Only the walkproc method has been implimented since it is functionally equivelent to the walk method. Note that the command line options have been moved after the cmdprefix. * NOT YET IMPLEMENTED >> ::struct::tree::prune >> treeName walk node ?-order order? ?-type type? loopvar script Tree debugging methods ================================== The following methods were added to help with debug the Tree class. The node name/node instance relationship is kept in an index. This commands allow you to see what Node class instance is associated with a node name. ${treeName} ptree ?name? Pretty print the name structure of a tree. If a node name is provided then just the subtree starting at node is printed. ${treeName} pnodes Pretty print the instances of the Node class in a tree. ${treeName} pkeys ?name ..name? Pretty print the attribute key information for one or more node names. If no names are given then all nodes are printed. ${treeName} pstream stream Pretty print the contents of a stream produced by the serialize method. The Node class and methods ================================== The Node class is a simple class that contains a parent, a list of children and a set of attributes. This class supports the following methods. ${node} parent ?pnode? If 'pnode' isn't blank, set the node's parent to its value; return the current parent. ${node} children ?clist? ?-force? If 'clist' isn't blank, set the node's children to its value; return the current children list. Use the -force option to set the children to a blank list. ${node} insert index ?node ...node? Insert a list of node instances ('args') into the list of children at location 'index'. ${node} attrs ?kdict? ?-force? If 'kdict' isn't blank set the node attributes to its value; return the current value of attributes. Use the -force option to set the attribute list to blank. ${node} attrs.filter ?globpat? Return the node's attributes as a dict of key/value pairs. If globpat exists, only keys that match the glob pattern will be returned. ${node} attr.keys ?globpat? Return the node's attribute keys as a list. If globpat exists, only return keys that match the glob pattern. ${node} attr.set key value Set the value of the attribute 'key' to 'value'. If 'key doesn't exist add it to the node. ${node} attr.unset key Unset the attribute 'key' of node. ${node} attr.exists key Return true of attribute 'key' exists for node else return false. ${node} attr.get key Return the value of the attribute 'key' for node. ${node} attr.append key value Do a string append of 'value' to the value of attribute 'key' for node. Return the resulting string value. ${node} attr.lappend key value Do a list append of 'value' to the value of attribute 'key' for node. Return the resulting list value.
package provide Tree 0.1 package require Tcl 8.6 package require TclOO # -- # Debugging method for TclOO object instance. oo::define oo::object method debug {{pat *}} { set res [list class [info object class [self]]] foreach i [info object vars [self] $pat] { my variable $i lappend res $i [set $i] } set res } ##### NODE ############################################################## # -- # parent - contains the parent node instance # children - contains list of child node instances # attrs - a dictionary of attribute/value pairs oo::class create Node { # -- # create a named node constructor { pnode } { my variable parent my variable children my variable attrs set parent ${pnode} set children {} set attrs [dict create] } # -- # If 'inst' isn't blank, set the node's parent to its # value; return the current parent. method parent { {inst ""} } { my variable parent if { ${inst} ne "" } { set parent ${inst} } return ${parent} } # -- # If 'new' isn't blank, set the node's children to its # value; return the current childern list. method children { {new ""} {force ""} } { my variable children if { [llength ${new}] != 0 || ${force} eq "-force" } { set children ${new} } return ${children} } # -- # Insert a list of node instances ('args') into the # child list at location 'index'. method insert { index args } { my variable children set children [linsert ${children} ${index} {*}${args}] return } # -- # If 'new' isn't blank set the node attributes to its # value; return the current value of attributes. method attrs { {new ""} {force ""} } { my variable attrs if { ${new} ne "" || ${force} eq "-force" } { set attrs ${new} } return ${attrs} } method attrs.filter { {globpat ""} } { my variable attrs if { ${globpat} eq "" } { return ${attrs} } else { return [dict filter ${attrs} key ${globpat}] } } method attr.keys { {globpat ""} } { my variable attrs if { ${globpat} eq "" } { return [dict keys ${attrs}] } else { return [dict keys ${attrs} ${globpat}] } } method attr.set { attr value } { my variable attrs dict set attrs ${attr} ${value} return ${value} } method attr.unset { attr } { my variable attrs dict unset attrs ${attr} return } method attr.exists { attr } { my variable attrs return [dict exist ${attrs} ${attr}] } method attr.get { attr } { my variable attrs if { [dict exist ${attrs} ${attr}] } { return [dict get ${attrs} ${attr}] } error "attribute '${attr}' - not found" } method attr.append { attr value } { my variable attrs dict append attrs ${attr} ${value} return } method attr.lappend { attr value } { my variable attrs dict lappend attrs ${attr} ${value} return } } ##### TREE ############################################################## # -- # # nid - integer value used to create unique node names # root - name of tree's root node # nodes - index of node names and node instances # oo::class create Tree { self export varname constructor { } { my variable root my variable nodes my eval upvar [[self class] varname nid] nid set nid 0 set root "root" set nodes [dict create "root" [Node new ""]] } destructor { variable nodes dict for {name node} ${nodes} { ${node} destroy } } ##### PRIVITE ############################## # -- # used by debugging utility method DumpSubtree { parent {indent 0} } { set pnode [my Name2Node ${parent}] puts "[format "%-12s" ${pnode}]- [string repeat { } ${indent}]${parent}" incr indent foreach child [${pnode} children] { my DumpSubtree [my Node2Name ${child}] ${indent} } } # -- # check args for a node that exists and return its name # else return "" method NotUsed { args } { my variable nodes foreach name ${args} { if { [dict exists ${nodes} ${name}] } { return ${name} } } return "" } # -- # return a node instance given a node name method Name2Node { name } { my variable nodes return [dict get ${nodes} ${name}] } # -- # return a node name given a node instance method Node2Name { node } { my variable nodes dict for {name node} [dict filter ${nodes} value ${node}] { return ${name} } error "node (${node}) - not found" } # -- # return a list of node instances given a list of node names method Names2NodeList { args } { set nlist {} foreach name ${args} { lappend nlist [my Name2Node ${name}] } return ${nlist} } # -- # return a list of node names given a list of node instances method Nodes2NameList { args } { set nlist {} foreach node ${args} { lappend nlist [my Node2Name ${node}] } return ${nlist} } # -- # return the list of all nodes below parent node # optionaly filter nodes useing procedure 'filter' method GetSubtree { parent {filter ""} } { my variable nodes set pnode [my Name2Node ${parent}] set children [my Nodes2NameList {*}[${pnode} children]] set subtree "" foreach child ${children} { if { ${filter} eq "" || [eval [list ${filter} [self object] ${child} [dict get ${nodes} ${child}]]] == 0 } { lappend subtree ${child} lappend subtree {*}[my GetSubtree ${child} ${filter}] } } return ${subtree} } # -- # completely delete one node method DeleteNode { name } { my variable root my variable nodes set node [my Name2Node ${name}] # delete node from index set nodes [dict remove ${nodes} ${name}] # create a new root node if it was deleted if { ${name} eq ${root} } { dict set nodes ${name} [Node new ""] } ${node} destroy } # -- # replace the child entry for 'name' in its parent # with 0 or more new children method ReplaceParentLink { name args } { set cnode [my Name2Node ${name}] set pnode [${cnode} parent] if { ${pnode} eq "" } { return } set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] if { ${idx} < 0 } { error "node (${name}) - not found" } if { [llength ${args}] == 0 } { set children [lreplace ${children} ${idx} ${idx}] } else { set nlist [my Names2NodeList {*}${args}] set children [lreplace ${children} ${idx} ${idx} {*}${nlist}] } ${pnode} children ${children} -force } # -- # Serialize a node and add it to stream. # # The result is a 3 element list haveing the following entries. # # 1) node name # 2) the node's attributes in dictionary form # 3) a recursive serialization of all children of the node # method SerializeNode { stream name {isroot 0}} { my variable root my variable nodes # serialize the children set children {} foreach child [my children ${name}] { lappend children {*}[my SerializeNode ${stream} ${child}] } set node [my Name2Node ${name}] lappend stream ${name} [${node} attrs.filter] ${children} return ${stream} } # -- # Unlink a list of nodes from their parents. Note that a node # may be in the subtree of a node that is being unlinked. method UnlinkNodes { args } { set notfound [my exists {*}${args}] if { ${notfound} ne "" } { error "node (${notfound}) - not found" } # Break the links to the parents foreach name ${args} { my ReplaceParentLink ${name} set pnode [my Name2Node ${name}] ${pnode} parent "" } } # -- Pstream # Pretty print a node from a serialization stream. method Pstream { name attrs children indent } { set pad [string repeat " " ${indent}] puts "${pad}${name}" puts "${pad} ${attrs}" incr indent foreach {n a c} ${children} { my Pstream ${n} ${a} ${c} ${indent} } } # -- # method DeserializeNode { name pnode attrs children } { my variable nodes # create the a node and set its parent set node [Node new ${pnode}] # add the node to the index dict set nodes ${name} ${node} # set the node's attributes ${node} attrs ${attrs} # create all the children for the node set cnodes {} foreach {n a c} ${children} { lappend cnodes [my DeserializeNode ${n} ${node} ${a} ${c}] } ${node} children ${cnodes} -force return ${node} } ##### PUBLIC ############################## # -- ptree # debugging utility method ptree { {name ""} } { my variable root if { ${name} eq "" } { my DumpSubtree ${root} } else { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } my DumpSubtree ${name} } } # -- pnodes # debugging utility method pnodes { } { my variable nodes foreach name [lsort -dictionary [dict keys ${nodes}]] { set node [dict get ${nodes} ${name}] set pnode [${node} parent] set children [my children ${name}] puts [format "(%-12s) %-12s %s -> %s" ${pnode} ${node} ${name} [join ${children} {, }]] } } # -- pkeys # debugging utility method pkeys { args } { if { [llength ${args}] == 0 } { set args [my nodes] } else { set notfound [my exists {*}${args}] if { ${notfound} ne "" } { error "node (${notfound}) - not found" } } foreach name ${args} { set node [my Name2Node ${name}] puts "node(${name})" set width 0 foreach key [${node} attr.keys] { set len [string length ${key}] if { ${len} > ${width} } { set width ${len} } } foreach {key val} [${node} attrs.filter] { puts " [format "%-${width}s" ${key}]: '${val}'" } } } # -- pstream # debugging utility method pstream { stream } { lassign ${stream} name attrs children my Pstream ${name} ${attrs} ${children} 0 } # -- # method ancestors { child } { if { [my exists ${child}] ne "" } { error "node (${child}) - not found" } set ancestors {} while { true } { set ancestor [my parent ${child}] if { ${ancestor} eq "" } { break } else { lappend ancestors ${ancestor} set child ${ancestor} } } return ${ancestors} } # -- # method children { parent } { my variable nodes if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } set pnode [my Name2Node ${parent}] set children [${pnode} children] return [my Nodes2NameList {*}${children}] } # -- # Remove a node from the tree and move its # children into the parent. Ignore cut on # the root node. method cut { name {opt ""} } { my variable nodes if { ${name} eq [my rootname] } { return } if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } # get the children for the node set children [my children ${name}] # replace the node with its childer in the parent my ReplaceParentLink ${name} {*}${children} if { ${opt} eq "-delete" } { # delete the node set node [my Name2Node ${name}] dict unset nodes ${name} ${node} destroy } return } # -- # method delete { args } { set notfound [my exists {*}${args}] if { ${notfound} ne "" } { error "node (${notfound}) - not found" } # Remove all the subtree nodes. # This code accounts for the possibility that # one of the args is in the subtree of another arg. set names {} foreach name ${args} { lappend names {*}[my descendants ${name}] } foreach name [lsort -unique ${names}] { my DeleteNode ${name} } # Now remove the nodes themselves and their child # entry in their parent foreach name ${args} { my ReplaceParentLink ${name} my DeleteNode ${name} } return } # -- # method depth { name } { return [llength [my ancestors ${name}]] } # -- # method descendants { parent {opt ""} {arg ""} } { my variable nodes if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } if { ${opt} eq "-filter" } { set filter ${arg} return [my GetSubtree ${parent} ${filter}] } else { return [my GetSubtree ${parent}] } } # -- # Replace the attribute and subtree definitions of node # 'lname' with the definitions found in 'stream'. The 'lname' # node must be a leaf node unless the '-force' option is is # used. method deserialize { lname stream {opt ""}} { my variable root my variable nodes if { [my exists ${lname}] ne "" } { error "node (${lname}) - not found" } if { ${opt} eq "-force" } { # force lname to be a leaf set parent [my parent ${lname}] my delete ${lname} set node [Node new [my Name2Node ${parent}]] dict set nodes ${lname} ${node} } if { ![my isleaf ${lname}] } { error "node (${lname}) - is not a leaf node" } # get the leaf node set lnode [my Name2Node ${lname}] # get the root of the serialization lassign ${stream} rname attrs children # put attributes in the leaf node ${lnode} attrs ${attrs} # deserialize all the children into the leaf node set cnodes {} foreach {n a c} ${children} { lappend cnodes [my DeserializeNode ${n} ${lnode} ${a} ${c}] } ${lnode} children ${cnodes} -force return } # -- # return "" if all exist else return name that isn't found method exists { args } { my variable nodes foreach name ${args} { if { ![dict exists ${nodes} ${name}] } { return ${name} } } return "" } # -- # method index { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] return [lsearch -exact ${children} ${cnode}] } # -- # method insert { parent index args } { my variable nid my variable nodes if { [llength ${args}] == 0 } { incr nid set args "node${nid}" } else { if { ${parent} in ${args} } { error "parent (${parent}) - found in insert list" } } set pnode [my Name2Node ${parent}] set nlist "" foreach name ${args} { if { [my exists ${name}] ne "" } { # create a new child that references the parent set node [Node new ${pnode}] # add the node to the index dict set nodes ${name} ${node} } else { # child already exists so it must be cut from its # current location my UnlinkNodes ${name} set node [my Name2Node ${name}] ${node} parent ${pnode} } lappend nlist ${node} } # insert the list of child nodes into the # parent's list of children if { [llength ${nlist}] > 0 } { ${pnode} insert ${index} {*}${nlist} } return ${args} } # -- # method isleaf { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [expr ( [llength [${node} children]] > 0 ) ? 0 : 1] } # -- # method keys { {name ""} {gpat ""} } { if { ${name} eq "" } { set nlist [my nodes] } else { set nlist ${name} } set result {} foreach name ${nlist} { set node [my Name2Node ${name}] if { ${gpat} eq "" } { lappend result {*}[${node} attr.keys] } else { set d [dict create {*}[${node} attrs.filter ${gpat}]] lappend result {*}[dict keys ${d}] } } return [lsort -unique ${result}] } # -- # method key.append { name key value } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.append ${key} ${value} return } # -- # method key.exists { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attr.exists ${key}] } # -- # method key.get { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attr.get ${key}] } # -- # method key.getall { name {globpat ""} } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attrs.filter ${globpat}] } # -- # method key.lappend { name key value } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.lappend ${key} ${value} return [${node} attr.get ${key}] } # -- # method key.nodes { key {flag ""} {arg ""} } { set result {} set names [my nodes] switch -exact ${flag} { "-nodes" { set names ${arg} } "-glob" { set nlist {} set gpat ${arg} foreach name ${names} { if { [string match ${gpat} ${name}] == 1 } { lappend nlist ${name} } } set names ${nlist} } "-regexp" { set nlist {} set rpat ${arg} foreach name ${names} { if { [regexp ${rpat} ${name}] == 1 } { lappend nlist ${name} } } set names ${nlist} } default { }} foreach name ${names} { if { [my key.exists ${name} ${key}] } { lappend result ${name} [my key.get ${name} ${key}] } } return ${result} } # -- # method key.set { name key args } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] if { [llength ${args}] == 1 } { ${node} attr.set ${key} [lindex ${args} 0] } return [${node} attr.get ${key}] } # -- # method key.unset { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.unset ${key} } # -- # method leaves { } { set leaves {} foreach name [my nodes] { if { [my isleaf ${name}] == 1 } { lappend leaves ${name} } } return ${leaves} } # -- # method move { parent index args } { set pnode [my Name2Node ${parent}] # Make sure the list of nodes doesn't contain an # ancestor of the parent. If this were allowed the # subtree would become disconnected. set alist [my ancestors ${parent}] foreach name ${args} { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } if { ${name} in ${alist} } { error "node (${name}) is an ancestor of node (${parent})" } } # unlink the nodes set nlist {} foreach name ${args} { my UnlinkNodes ${name} set node [my Name2Node ${name}] ${node} parent ${pnode} lappend nlist ${node} } # link the nodes into the parent at location 'index' set children [${pnode} children] ${pnode} children [linsert ${children} ${index} {*}${nlist}] return } # -- # method next { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] incr idx if { ${idx} < [llength ${children}] } { return [my Node2Name [lindex ${children} ${idx}]] } else { return "" } } # -- # method numchildren { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [llength [${node} children]] } # -- # method nodes { } { my variable nodes return [dict keys ${nodes}] } # -- # method parent { child } { my variable nodes if { [my exists ${child}] ne "" } { error "node (${child}) - not found" } set cnode [my Name2Node ${child}] set pnode [${cnode} parent] if { ${pnode} eq "" } { return "" } else { return [my Node2Name ${pnode}] } } # -- # method previous { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] incr idx -1 if { ${idx} >= 0 } { return [my Node2Name [lindex ${children} ${idx}]] } else { return "" } } # -- # method rename { from to } { my variable root my variable nodes if { ![dict exists ${nodes} ${from}] } { error "node (${to}) - not found" } if { [dict exists ${nodes} ${to}] } { error "node (${to}) - already exists" } set node [dict get ${nodes} ${from}] set nodes [dict remove ${nodes} ${from}] dict set nodes ${to} ${node} if { ${from} eq ${root} } { set root ${to} } return } # -- # method rootname { } { my variable root return ${root} } # -- # Return a serialization of the subtree starting at 'name'. # # The result is a list containing three element. The elements # are (1) a node name (2) the node's attributes in dictionary # form (3) zero or more additional three element lists that # recursivly serialize the children of the node. # method serialize { name } { my variable root my variable nodes if { ${name} ne "root" && [my exists ${name}] ne "" } { error "node (${name}) - not found" } # create the null node set stream {} set stream [my SerializeNode ${stream} ${name} 1] return ${stream} } # -- # method size { {name ""} } { if { ${name} eq "" } { set name [my rootname] } else { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } } return [llength [my descendants ${name}]] } # -- # method splice { parent from {to ""} {child ""} } { my variable nid my variable nodes if { ${parent} eq "root" } { set parent [my rootname] } else { if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } } if { ${to} eq "" } { set to "end" } if { ${child} eq "" } { incr nid set child "node${nid}" } else { if { [my NotUsed ${child}] ne "" } { error "node (${child}) - already exists" } } # get the parent information set pnode [my Name2Node ${parent}] # create the new child set node [Node new ${pnode}] # add the node to the index dict set nodes ${child} ${node} # get the parents children set children [${pnode} children] # put the range of childern in the new node ${node} children [lrange ${children} ${from} ${to}] -force # remove the range of children from the parent and insert the new node ${pnode} children [lreplace ${children} ${from} ${to} ${node}] -force return ${child} } # -- # method swap { name1 name2 } { if { ${name1} eq ${name2} } { return } # make sure the nodes exist if { [my exists ${name1}] ne "" } { error "node (${name1}) - not found" } if { [my exists ${name2}] ne "" } { error "node (${name2}) - not found" } # make sure one node isn't in the the other node's subtree # (this also precludes a swap with 'root') set node1 [my Name2Node ${name1}] set node2 [my Name2Node ${name2}] if { [lsearch -exact [my descendants ${name1}] ${name2}] != -1 } { error "node (${name2}) in subtree of node (${name1})" } if { [lsearch -exact [my descendants ${name2}] ${name1}] != -1 } { error "node (${name1}) in subtree of node (${name2})" } # check to see if the nodes have a common parent set pnode1 [${node1} parent] set pnode2 [${node2} parent] if { ${pnode1} eq ${pnode2} } { # nodes have a common parent node set children [${pnode1} children] set idx1 [lsearch -exact ${children} ${node1}] set idx2 [lsearch -exact ${children} ${node2}] set children [lreplace ${children} ${idx1} ${idx1} ${node2}] set children [lreplace ${children} ${idx2} ${idx2} ${node1}] ${pnode1} children ${children} -force } else { # nodes have different parent nodes set children1 [${pnode1} children] set children2 [${pnode2} children] set idx1 [lsearch -exact ${children1} ${node1}] set idx2 [lsearch -exact ${children2} ${node2}] set children1 [lreplace ${children1} ${idx1} ${idx1} ${node2}] set children2 [lreplace ${children2} ${idx2} ${idx2} ${node1}] ${pnode1} children ${children1} -force ${pnode2} children ${children2} -force ${node1} parent ${pnode2} ${node2} parent ${pnode1} } return } ##### WALKPROC CODE (DEPTH FIRST) ############################ # -- # method DfsPreOrderWalk { name cmdprefix } { my variable nodes if { [catch {${cmdprefix} [self object] ${name} "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my PreOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } return 0 } # -- # method DfsPostOrderWalk { name cmdprefix } { my variable nodes my variable nodes set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my PostOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { [catch {${cmdprefix} [self object] ${name} "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } return 0 } # -- # method DfsBothOrderWalk { name cmdprefix } { my variable nodes if { [catch {${cmdprefix} [self object] ${name} "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my BothOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { [catch {${cmdprefix} [self object] ${name} "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } return 0 } # -- # method DfsInOrderWalk { name cmdprefix } { my variable nodes set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { if { ${idx} == 1 } { if { [catch {${cmdprefix} [self object] ${name} "visit"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my InOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { ${idx} == 0 } { if { [catch {${cmdprefix} [self object] ${name} "visit"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } ##### WALKPROC CODE (BREADTH FIRST) ############################ # -- # This method takes as input a list of nodes (nlist) and returns # a new list that is the list of all children for the input list. method DecendOneLevelForward { nlist } { set result {} foreach node ${nlist} { lappend result {*}[${node} children] } return ${result} } # -- # This method takes as input a list of nodes (nlist) and returns # a new list that is the list of all children for the input list. method DecendOneLevelBackward { nlist } { set result {} foreach node ${nlist} { lappend result {*}[lreverse [${node} children]] } return ${result} } # -- # method BfsPreOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } if { [my BfsPreOrderWalk [my DecendOneLevelForward ${nlist}] ${cmdprefix}] != 0 } { return 1 } return 0 } # -- # method BfsPostOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } if { [my BfsPostOrderWalk [my DecendOneLevelBackward ${nlist}] ${cmdprefix}] != 0 } { return 1 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } # -- # method BfsBothOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } my BfsBothOrderWalk [my DecendOneLevelForward ${nlist}] ${cmdprefix} foreach node [lreverse ${nlist}] { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } # -- # method BfsInOrderWalk { } { error "unable to do a in-order breadth first walk" } # -- # method walkproc { name cmdprefix args } { set types {bfs dfs} set orders {pre post both in} set type "dfs" set order "pre" if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } foreach {opt val} ${args} { switch -exact -- ${opt} { "-order" { if { ${val} ni ${orders} } { error "-order ${val} - must be oneof: [join ${orders} {, }]" } set order ${val} } "-type" { if { ${val} ni ${types} } { error "-type ${val} - must be oneof: [join ${types} {, }]" } set type ${val} } default { }} } if { ${type} eq "dfs" } { switch -exact -- ${order} { "post" { my DfsPostOrderWalk ${name} ${cmdprefix} } "both" { my DfsBothOrderWalk ${name} ${cmdprefix} } "in" { my DfsInOrderWalk ${name} ${cmdprefix} } "pre" - default { my DfsPreOrderWalk ${name} ${cmdprefix} }} } else { switch -exact -- ${order} { "post" { my BfsPostOrderWalk [my Name2Node ${name}] ${cmdprefix} } "both" { my BfsBothOrderWalk [my Name2Node ${name}] ${cmdprefix} } "in" { my BfsInOrderWalk } "pre" - default { my BfsPreOrderWalk [my Name2Node ${name}] ${cmdprefix} }} } return } }
dkf - 2009-07-15 11:33:18
Do trees own nodes (I'm talking UML composition vs. aggregation here). If a node is never shared between trees or otherwise exposed, there are more effective ways of managing the creation of nodes that make cleanup of the whole tree simpler. (For example, the node instances could be renamed so that they are in the tree instance's namespace, which makes destruction automatic.)
TJK : I'm not sure that I understand your "composition vs. aggregation" comment (being a EE not a CS major) but I believe you are asking if a tree instance could "contain" all the node instances. The answer to that question is yes, but my implementation doesn't make use of this relation ship. I split out nodes from the tree to help keep track of what I was doing in the code. I'm a neophyte OO programmer so I doubt that this is close to being a good implementation (from an OO point of view) so please modify the code. My initial thought was that I would create an inheritance hierachy that looked like NODE -> NODES -> TREE. But there was so little documentation on TclOO I decided not to tackle learning about inheritance (filters, mixins, etc.) during my first test drive.
lm 16/07/2009 : Any idea about performances of this package compare to the ::struct::tree one ?
DKF: Not tried yet. Up to my eyeballs in other things at the moment...
TJK : I did do some quick tests of the performance and found that tree creation is slower by about 10 to 20 percent but tree deletion is very slow. My quick tests indicated that tree deletion degraded quickly with tree depth and became several orders of magnitude slower as the tree depth reached 8. I think this my be related to the implementation as discussed above.