Tree::DAG_Node(3pm)
๐ณ Tree::DAG_Node โ an N-ary tree, bestie edition ๐
okay so. before we even get into it: the "official" name of this whole deal is Tree::DAG_Node(3pm), and it lives in the User Contributed Perl Documentation section. That's it. That's the header. Now let's gooo. โจ
๐ NAME ๐
Tree::DAG_Node โ An N-ary tree. that's it. that's the whole vibe. ๐ธ
โจ SYNOPSIS โจ
๐ง Using as a base class
package Game::Tree::Node;
use parent 'Tree::DAG_Node';
# Now add your own methods overriding/extending the methods in C<Tree::DAG_Node>...
๐ Using as a class on its own
use Tree::DAG_Node;
my($root) = Tree::DAG_Node -> new({name => 'root', attributes => {uid => 0} });
$root -> add_daughter(Tree::DAG_Node -> new({name => 'one', attributes => {uid => 1} }) );
$root -> add_daughter(Tree::DAG_Node -> new({name => 'two', attributes => {} }) );
$root -> add_daughter(Tree::DAG_Node -> new({name => 'three'}) ); # Attrs default to {}.
Or:
my($count) = 0;
my($tree) = Tree::DAG_Node -> new({name => 'Root', attributes => {'uid' => $count} });
Or:
my $root = Tree::DAG_Node -> new();
$root -> name("I'm the tops");
$root -> attributes({uid => 0});
my $new_daughter = $root -> new_daughter;
$new_daughter -> name('Another node');
$new_daughter -> attributes({uid => 1});
...
Lastly, for fancy wrappers โ called _add_daughter() โ around new(), see these modules: Marpa::Demo::StringParser and GraphViz2::Marpa. Both of these modules use Moo. yes, Moo. iconic. ๐
See scripts/*.pl for other samples.
๐ฆ Using with utf-8 data
read_tree($file_name) works with utf-8 data. See t/read.tree.t and t/tree.utf8.attributes.txt. Such a file can be created by redirecting the output of tree2string() to a file of type utf-8.
See the docs for Encode for the difference between utf8 and utf-8. in brief? use utf-8. no cap. ๐
See also scripts/write_tree.pl and scripts/read.tree.pl and scripts/read.tree.log.
๐ DESCRIPTION ๐
This class encapsulates/makes/manipulates objects that represent nodes in a tree structure. The tree structure isn't an object itself โ it's emergent from the linkages you create between nodes, which is such a main character move honestly. This class gives you the methods for making linkages that you can use to build up a tree, while preventing you from ever making any kinds of linkages which are not allowed in a tree (like: a node being its own mother or ancestor, or having two mothers). it simply Will Not Let You. ๐ซ
This is what I mean by a "tree structure", a bit redundantly stated:
- A tree is a special case of an acyclic directed graph
- A tree is a network of nodes where there's exactly one root node
- Also, the only primary relationship between nodes is the mother-daughter relationship.
- No node can be its own mother, or its mother's mother, etc
- Each node in the tree has exactly one parent
- Except for the root of course, which is parentless.
- Each node can have any number (0 .. N) daughter nodes
- A given node's daughter nodes constitute an ordered list.
- However, you are free to consider this ordering irrelevant. Some applications do need daughters to be ordered, so I chose to consider this the general case.
- A node can appear in only one tree, and only once in that tree
- Notably (notable because it doesn't follow from the two above points), a node cannot appear twice in its mother's daughter list.
- There's an idea of up versus down
- Up means towards to the root, and down means away from the root (and towards the leaves).
- There's an idea of left versus right
- Left is toward the start (index 0) of a given node's daughter list, and right is toward the end of a given node's daughter list.
Trees as described above have various applications, among them: representing syntactic constituency, in formal linguistics; representing contingencies in a game tree; representing abstract syntax in the parsing of any computer language โ whether in expression trees for programming languages, or constituency in the parse of a markup language document. (Some of these might not use the fact that daughters are ordered.)
(Note: B-Trees are a very special case of the above kinds of trees, and are best treated with their own class. Check CPAN for modules encapsulating B-Trees; or if you actually want a database, and for some reason ended up looking here, go look at AnyDBM_File.) bestie, wrong aisle, but I respect the curiosity ๐ซถ
Many base classes are not usable except as such โ but Tree::DAG_Node can be used as a normal class. You can go ahead and say:
use Tree::DAG_Node;
my $root = Tree::DAG_Node->new();
$root->name("I'm the tops");
$new_daughter = Tree::DAG_Node->new();
$new_daughter->name("More");
$root->add_daughter($new_daughter);
and so on, constructing and linking objects from Tree::DAG_Node and making useful tree structures out of them.
๐ A NOTE TO THE READER ๐
This class is big and provides lots of methods. If your problem is simple (say, just representing a simple parse tree), this class might seem like using an atomic sledgehammer to swat a fly. But the complexity of this module's bells and whistles shouldn't detract from the efficiency of using this class for a simple purpose. In fact, I'd be very surprised if any one user ever had use for more that even a third of the methods in this class. And remember: an atomic sledgehammer will kill that fly. โ๏ธ๐ชฐ not me thinking that's the most metal sentence in the whole page
๐ OBJECT CONTENTS ๐
Implementationally, each node in a tree is an object, in the sense of being an arbitrarily complex data structure that belongs to a class (presumably Tree::DAG_Node, or ones derived from it) that provides methods.
The attributes of a node-object are:
- mother โ this node's mother.
undefif this is a root - daughters โ the (possibly empty) list of daughters of this node
- name โ the name for this node
- Need not be unique, or even printable. This is printed in some of the various dumper methods, but it's up to you if you don't put anything meaningful or printable here.
- attributes โ whatever the user wants to use it for
- Presumably a hashref to whatever other attributes the user wants to store without risk of colliding with the object's real attributes. (Example usage: attributes to an SGML tag โ you definitely wouldn't want the existence of a
mother=foopair in such a tag to collide with a node object'smotherattribute.) - Aside from (by default) initializing it to
{}, and having the access method calledattributes(described a ways below), I don't do anything with theattributesin this module. I basically intended this so that users who don't want/need to bother deriving a class fromTree::DAG_Node, could still attach whatever data they wanted in a node.
- Presumably a hashref to whatever other attributes the user wants to store without risk of colliding with the object's real attributes. (Example usage: attributes to an SGML tag โ you definitely wouldn't want the existence of a
mother and daughters are attributes that relate to linkage โ they are never written to directly, but are changed as appropriate by the "linkage methods", discussed below.
The other two (and whatever others you may add in derived classes) are simply accessed thru the same-named methods, discussed further below.
๐ About The Documented Interface ๐
Stick to the documented interface (and comments in the source โ especially ones saying "undocumented!" and/or "disfavored!" โ do not count as documentation!), and don't rely on any behavior that's not in the documented interface.
Specifically, unless the documentation for a particular method says "this method returns thus-and-such a value", then you should not rely on it returning anything meaningful.
A passing acquaintance with at least the broader details of the source code for this class is assumed for anyone using this class as a base class โ especially if you're overriding existing methods, and definitely if you're overriding linkage methods.
๐จ real talk: overriding linkage methods without knowing what you're doing is giving "I read one tutorial and now I'm a surgeon". please go read the source first. ๐
โญ MAIN CONSTRUCTOR, AND INITIALIZER โญ
- the constructor
CLASS->new()orCLASS->new($options)โ This creates a new node object, calls$object->_init($options)to provide it sane defaults (like: undef name, undef mother, no daughters,attributessetting of a new empty hashref), and returns the object created. (If you just saidCLASS->new()orCLASS->new, then it pretends you calledCLASS->new({}).)
See also the comments under new($hashref) for options supported in the call to new().
If you use Tree::DAG_Node as a superclass, and you add attributes that need to be initialized, what you need to do is provide an _init method that calls $this->SUPER::_init($options) to use its superclass's _init method, and then initializes the new attributes:
sub _init {
my($this, $options) = @_[0,1];
$this->SUPER::_init($options); # call my superclass's _init to
# init all the attributes I'm inheriting
# Now init /my/ new attributes:
$this->{'amigos'} = []; # for example
}
- the constructor
$obj->new()or$obj->new($options)โ Just another way to get at thenew($hashref)method. This does not copy$obj, but merely constructs a new object of the same class as it. Saves you the bother of going$class = ref $obj; $obj2 = $class->new; - the method
$node->_init($options)โ Initialize the object's attribute values. See the discussion above. Presumably this should be called only by the guts of thenew($hashref)constructor โ never by the end user.
Currently there are no documented options for putting in the $options hashref, but (in case you want to disregard the above rant) the option exists for you to use $options for something useful in a derived class.
Please see the source for more information.
- see also (below) the constructors
new_daughterandnew_daughter_left
๐ธ METHODS ๐ธ
add_daughter(LIST)
An exact synonym for add_daughters(LIST). cute, same energy, different name ๐
add_daughters(LIST)
This method adds the node objects in LIST to the (right) end of $mother's daughter list. Making a node N1 the daughter of another node N2 also means that N1's mother attribute is "automatically" set to N2; it also means that N1 stops being anything else's daughter as it becomes N2's daughter.
If you try to make a node its own mother, a fatal error results. If you try to take one of a node N1's ancestors and make it also a daughter of N1, a fatal error results. A fatal error results if anything in LIST isn't a node object.
If you try to make N1 a daughter of N2, but it's already a daughter of N2, then this is a no-operation โ it won't move such nodes to the end of the list or anything; it just skips doing anything with them.
add_daughter_left(LIST)
An exact synonym for add_daughters_left(LIST).
add_daughters_left(LIST)
This method is just like add_daughters(LIST), except that it adds the node objects in LIST to the (left) beginning of $mother's daughter list, instead of the (right) end of it. โจ left side supremacy โจ
add_left_sister(LIST)
An exact synonym for add_left_sisters(LIST).
add_left_sisters(LIST)
Okay so bestie, this one adds the elements in LIST โ in that exact order, no shuffling โ as immediate left sisters of $node. In other words: given that B's mother's daughter-list is (A,B,C,D), calling B->add_left_sisters(X,Y) makes B's mother's daughter-list (A,X,Y,B,C,D). Iconic behavior honestly. ๐
If LIST is empty, this is a no-op, and returns empty-list. Nothing happened, nothing will happen, we move on. ๐
Heads up though: this is basically implemented as a call to $node->replace_with(LIST, $node), and so all replace_with's limitations and caveats apply. Read those first, fr fr.
The return value of $node->add_left_sisters(LIST) is the elements of LIST that got added, as returned by replace_with โ minus the copies of $node you'd get from a straight call to $node->replace_with(LIST, $node). Yes, that's very specific. No, I'm not paraphrasing it. ๐
add_right_sister(LIST)
An exact synonym for "add_right_sisters(LIST)". Same method, different outfit. Slay. โจ
add_right_sisters(LIST)
Just like add_left_sisters (which see ๐), except that the elements in LIST go in (in that order) as immediate right sisters of $node.
In other words, given that B's mother's daughter-list is (A,B,C,D), calling B->add_right_sisters(X,Y) makes B's mother's daughter-list (A,B,X,Y,C,D). ๐
address() / address(ADDRESS)
With the first syntax, returns the address of $node within its tree, based on its position within the tree. An address is formed by noting the path between the root and $node, and concatenating the daughter-indices of the nodes this passes thru (starting with 0 for the root, and ending with $node).
For example, if to get from node ROOT to node $node, you pass thru ROOT, A, B, and $node, then the address is determined as:
ROOT'smy_daughter_indexis0A'smy_daughter_indexis, suppose,2Ais index2inROOT's daughter list.
B'smy_daughter_indexis, suppose,0Bis index0inA's daughter list.
$node'smy_daughter_indexis, suppose,4$nodeis index4inB's daughter list.
The address of the above-described $node is, therefore, "0:2:0:4".
(As a somewhat special case, the address of the root is always "0"; and since addresses start from the root, all addresses start with a "0".)
The second syntax, where you provide an address, starts from the root of the tree $anynode belongs to, and returns the node corresponding to that address. Returns undef if no node corresponds to that address. Note that this routine may be somewhat liberal in its interpretation of what can constitute an address; i.e., it accepts "0.2.0.4", besides "0:2:0:4".
Also note that the address of a node in a tree is meaningful only in that tree as currently structured.
(Consider how ($address1 cmp $address2) may be magically meaningful to you, if you meant to figure out what nodes are to the right of what other nodes.) ๐ฎ
ancestors()
Returns the list of this node's ancestors, starting with its mother, then grandmother, and ending at the root. It does this by simply following the 'mother' attributes up as far as it can. So if $item IS the root, this returns an empty list.
Consider that scalar($node->ancestors) returns the ply of this node within the tree โ 2 for a granddaughter of the root, etc., and 0 for root itself. Math girlies rise. ๐
attribute() / attribute(SCALAR)
Exact synonyms for "attributes()" and "attributes(SCALAR)". No thoughts, just vibes, just aliases. ๐
attributes() / attributes(SCALAR)
In the first form, returns the value of the node object's "attributes" attribute. In the second form, sets it to the value of SCALAR. I intend this to be used to store a reference to a (presumably anonymous) hash the user can use to store whatever attributes he doesn't want to have to store as object attributes. In this case, you needn't ever set the value of this. (_init has already initialized it to {}.) Instead you can just do...
$node->attributes->{'foo'} = 'bar';
...to write foo => bar. ๐
clear_daughters()
This unlinks all $mother's daughters. Returns the list of what used to be $mother's daughters.
Not to be confused with "remove_daughters(LIST)". Different method, different whole entire vibe. Do not. ๐ญ
common(LIST)
Returns the lowest node in the tree that is ancestor-or-self to the nodes $node and LIST.
If the nodes are far enough apart in the tree, the answer is just the root.
If the nodes aren't all in the same tree, the answer is undef.
As a degenerate case, if LIST is empty, returns $node.
common_ancestor(LIST)
Returns the lowest node that is ancestor to all the nodes given (in nodes $node and LIST). In other words, it answers the question: "What node in the tree, as low as possible, is ancestor to the nodes given ($node and LIST)?"
If the nodes are far enough apart, the answer is just the root โ except if any of the nodes are the root itself, in which case the answer is undef (since the root has no ancestor).
If the nodes aren't all in the same tree, the answer is undef.
As a degenerate case, if LIST is empty, returns $node's mother; that'll be undef if $node is root.
copy($option)
Returns a copy of the calling node (the invocant). E.g.:
my($copy) = $node -> copy;
$option is a hashref of options, with these (key => value) pairs:
no_attribute_copy => $Booleanโ If set to1, do not copy the node's attributes. If not specified, defaults to0, which copies attributes.
copy_at_and_under() / copy_at_and_under($options)
This returns a copy of the subtree consisting of $node and everything under it. ๐
If you pass no options, copy_at_and_under pretends you've passed {}.
This works by recursively building up the new tree from the leaves, duplicating nodes using $orig_node->copy($options_ref) and then linking them up into a new tree of the same shape.
Options you specify are passed down to calls to $node->copy.
copy_tree() / copy_tree($options)
This returns the root of a copy of the tree that $node is a member of. If you pass no options, copy_tree pretends you've passed {}.
This method is currently implemented as just a call to $this->root->copy_at_and_under($options), but magic may be added in the future. โจ (manifesting.)
Options you specify are passed down to calls to $node->copy.
daughters()
This returns the (possibly empty) list of daughters for $node. That's it, that's the method. ๐
decode_lol($lol)
Returns an arrayref having decoded the deeply nested structure $lol.
$lol will be the output of either tree_to_lol() or tree_to_simple_lol().
See scripts/read.tree.pl, and it's output file scripts/read.tree.log.
delete_tree()
Bestie. Sit down. This one is a destructor.
It destroys the entire tree that $node is a member of (starting at the root), by nulling out each node-object's attributes (including, most importantly, its linkage attributes โ hopefully this is more than sufficient to eliminate all circularity in the data structure), and then moving it into the class DEADNODE. ๐
Use this when you're finished with the tree in question, and want to free up its memory. (If you don't do this, it'll get freed up anyway when your program ends.)
If you try calling any methods on any of the node objects in the tree you've destroyed, you'll get an error like:
Can't locate object method "leaves_under"
via package "DEADNODE".
So if you see that, that's what you've done wrong. (Actually, the class DEADNODE does provide one method: a no-op method "delete_tree". So if you want to delete a tree, but think you may have deleted it already, it's safe to call $node->delete_tree on it (again). ๐ฎโ๐จ)
The "delete_tree()" method is needed because Perl's garbage collector would never (as currently implemented) see that it was time to de-allocate the memory the tree uses โ until either you call $node->delete_tree, or until the program stops (at "global destruction" time, when everything is unallocated).
Incidentally, there are better ways to do garbage-collecting on a tree, ways which don't require the user to explicitly call a method like "delete_tree()" โ they involve dummy classes, as explained at <http://mox.perl.com/misc/circle-destroy.pod>
However, introducing a dummy class concept into "Tree::DAG_Node" would be rather a distraction. If you want to do this with your derived classes, via a DESTROY in a dummy class (or in a tree-metainformation class, maybe), then feel free to.
The only case where I can imagine "delete_tree()" failing to totally void the tree, is if you use the hashref in the "attributes" attribute to store (presumably among other things) references to other nodes' "attributes" hashrefs โ which 1) is maybe a bit odd, and 2) is your problem, because it's your hash structure that's circular, not the tree's. Anyway, consider:
# null out all my "attributes" hashes
$anywhere->root->walk_down({
'callback' => sub {
$hr = $_[0]->attributes; %$hr = (); return 1;
}
});
# And then:
$anywhere->delete_tree;
(I suppose "delete_tree()" is a "destructor", or as close as you can meaningfully come for a circularity-rich data structure in Perl.)
See also "WHEN AND HOW TO DESTROY THE TREE".
depth_under()
Returns an integer representing the number of branches between this $node and the most distant leaf under it. (In other words, this returns the ply of subtree starting of $node. Consider scalar($it->ancestors) if you want the ply of a node within the whole tree.)
descendants()
Returns a list consisting of all the descendants of $node. Returns empty-list if $node is a terminal_node.
(Note that it's spelled "descendants", not "descendents".) Spelling is self-care, bestie. ๐ซถ
draw_ascii_tree([$options])
Here, the [] refer to an optional parameter.
Returns an arrayref of lines suitable for printing. ๐จ๏ธ
Draws a nice ASCII-art representation of the tree structure.
The tree looks like:
|
<Root>
/-------+-----+---+---\
| | | | |
<I> <H> <D> <E> <B>
/---\ /---\ | | |
| | | | <F> <F> <C>
<J> <J> <J> <J> | |
| | | | <G> <G>
<K> <L> <K> <L>
| |
<M> <M>
| |
<N> <N>
| |
<O> <O>
See scripts/cut.and.paste.subtrees.pl.
Example usage:
print map("$_\n", @{$tree->draw_ascii_tree});
draw_ascii_tree() takes parameters you set in the $options hashref:
h_compactโ Takes0or1. Sets the extent to whichdraw_ascii_tree()tries to save horizontal space. If I think of a better scrunching algorithm, there'll be a"2"setting for this. Default:1.h_spacingโ Takes a number0or greater. Sets the number of spaces inserted horizontally between nodes (and groups of nodes) in a tree. Default:1.no_nameโ If true,draw_ascii_tree()doesn't print the name of the node; it simply prints a"*". Default:0(i.e., print the node name.)v_compactโ takes a number:0,1, or2. It sets how harddraw_ascii_tree()tries to save vertical space. Default?1. ๐
ngl the code sometimes hands you back trees that are lowkey cock-eyed in places ๐ญ if literally anyone out there has a better drawing algorithm, please, I am begging, I'd be so appreciative ๐ซถ
See also tree2string($options, [$some_tree]).
๐ dump_names($options)
Returns an array. โจ
It dumps the names of the nodes โ as an indented list, starting at $node and continuing on down under it. The options are:
_depthโ a nonnegative number Says what depth to consider$nodeas sitting at (so the generation under it is that plus one, etc.). You can go ahead and useset _depth => scalar($node->ancestors).
Default: 0.
tickโ a string to preface each entry with This string goes between the indenting-spacing and the node's name. You might prefer"*"or"-> ", or honestly whatever vibe you want. ๐ซ
Default: ''.
indentโ the string used to indent with Another sane value could be'. '(period, space). Setting it to an empty string suppresses indenting entirely.
Default: ' ' x 2.
The output is not printed, bestie โ it's returned as a list, where every item is a line with a "\n" hanging off the end of it.
๐ฆ format_node($options, $node)
Returns a string made of the node's name and, optionally, its attributes.
Possible keys in the $options hashref:
no_attributes => $Booleanโ if it's1, the node's attributes do not get included in the string that comes back.
Default: 0 (so yes, attributes are included).
Calls hashref2string($hashref).
Called by node2string($options, $node, $vert_dashes).
You would not normally call this method, fr fr.
If you don't feel like supplying options, just use format_node({}, $node).
๐ธ generation()
Returns a list of all the nodes (cruising left-to-right) that live in $node's generation โ meaning, they're the same number of nodes down from the root. $root->generation() is literally just $root. ๐
And obviously $node is always in its own generation. Periodt.
๐ง generation_under($node)
It's giving generation(), but it only returns the nodes in $node's generation that are also descendants of $node โ in other words:
@us = $node->generation_under( $node->mother->mother );
is all of $node's first cousins (borrowing yet more kinship terminology, bestie ๐) โ assuming $node really does have a grandmother. Actually, "cousins" isn't quite the right word, because @us ends up including $node's siblings and $node itself.
Okay so actually generation_under($node) is just an alias to generation(), but I figure that this:
@us = $node->generation_under($way_upline);
reads a bit nicer than this:
@us = $node->generation($way_upline);
But it's up to you. ๐
$node->generation_under($node) returns just $node.
If you call $node->generation_under($node) but NODE2 is not $node or an ancestor of $node, it just behaves like you called plain old $node->generation().
๐ hashref2string($hashref)
Returns the given hashref as a string. โจ
Called by format_node($options, $node).
๐ is_daughter_of($node2)
Returns true if and only if $node is a daughter of $node2. Right now it's implemented as just a test of ($it->mother eq $node2).
โญ is_node()
This always returns true. More importantly: $object->can('is_node') is true (regardless of what is_node() would do if you actually called it) for objects belonging to this class, or for any class derived from it. Slay. ๐
๐ is_root()
Returns 1 if the caller is the root, and 0 if it isn't. That's it, that's the whole method.
๐ leaves_under()
Returns a list (going left-to-right) of all the leaf nodes under $node. ("Leaf nodes" are also called "terminal nodes" โ i.e., nodes with no daughters.) In the degenerate case where $node is a leaf itself, you just get $node back.
๐ left_sister()
Returns the node that's the immediate left sister of $node. If $node is the leftmost (or only) daughter of its mother, or has no mother at all, then this returns undef.
See also add_left_sisters(LIST) and add_right_sisters(LIST).
๐ฆ left_sisters()
Returns a list of the nodes that're sisters to the left of $node. If $node is the leftmost (or only) daughter of its mother, or has no mother at all, then this returns an empty list.
See also add_left_sisters(LIST) and add_right_sisters(LIST).
๐ง lol_to_tree($lol)
This one must be called as a class method, no cap.
It converts something like bracket-notation for "Chomsky trees" (or rather, the closest you can possibly come with Perl list-of-lists(-of-lists(-of-lists))) into an actual tree structure. Returns the root of the converted tree. ๐ณ
The conversion rules are: 1) if the last item in a given list (possibly the only item) is a scalar, then that gets used as the name attribute for the node based on this list. 2) All the other items in the list represent daughter nodes of the current node โ recursively so, if they're list references; otherwise, (non-terminal) scalars are considered to denote nodes with that name. So ['Foo', 'Bar', 'N'] is an alternate way to represent [['Foo'], ['Bar'], 'N'].
An example will illustrate, bestie:
use Tree::DAG_Node;
$lol =
[
[
[ [ 'Det:The' ],
[ [ 'dog' ], 'N'], 'NP'],
[ '/with rabies\\', 'PP'],
'NP'
],
[ 'died', 'VP'],
'S'
];
$tree = Tree::DAG_Node->lol_to_tree($lol);
$diagram = $tree->draw_ascii_tree;
print map "$_\n", @$diagram;
...returns this tree:
|
<S>
|
/------------------\
| |
<NP> <VP>
| |
/---------------\ <died>
| |
<NP> <PP>
| |
/-------\ </with rabies\>
| |
<Det:The> <N>
|
<dog>
By the way (and honestly this kinda follows from the rules above), when you're denoting a LoL tree that consists of just one node, this:
$tree = Tree::DAG_Node->lol_to_tree( 'Lonely' );
is okay โจ although it'd probably occur to you to denote it only as:
$tree = Tree::DAG_Node->lol_to_tree( ['Lonely'] );
which is of course fine, too. Both icons. ๐
๐ธ mother()
Returns what node is $node's mother. This is undef if $node has no mother โ i.e., if it's a root. ๐ญ
See also is_root() and root().
๐ my_daughter_index()
Returns what index this daughter is, in its mother's daughter list. In other words, if $node is ($node->mother->daughters)[3], then $node->my_daughter_index returns 3.
As a special case, it returns 0 if $node has no mother.
โญ name()
โญ name(SCALAR)
In the first form, it returns the value of the node object's name attribute. In the second form, it sets it to the value of SCALAR. Simple as that. ๐ซถ
๐ new($hashref)
These options are supported in $hashref:
attributes=> a hashref of attributesdaughters=> an arrayref of nodesmother=> a nodename=> a string
See also MAIN CONSTRUCTOR, AND INITIALIZER for a long, juicy discussion on object creation. ๐
๐ new_daughter()
๐ new_daughter($options)
This constructs a new node (of the same class as $mother) and adds it to the (right) end of $mother's daughter list. It's basically the same as going:
$daughter = $mother->new;
$mother->add_daughter($daughter);
except it's rather more efficient, because (since $daughter is guaranteed to be brand new and not linked to or from anything) it doesn't have to check that $daughter isn't an ancestor of $mother, isn't already a daughter to some mother it'd need to be unlinked from, isn't already sitting in $mother's daughter list, etc. Efficiency queen. ๐
As you'd expect from a constructor, it returns the node-object it created.
Heads up: if you radically change themother/daughtersbookkeeping, you may have to change this routine, since it's one of the places that directly writes todaughtersandmother.
๐ง new_daughter_left()
๐ง new_daughter_left($options)
This is just like $mother->new_daughter, but it adds the new daughter to the left (the start) of $mother's daughter list. ๐
Heads up again: if you radically change themother/daughtersbookkeeping, you may have to change this routine, since it's one of the places that directly writes todaughtersandmother.
๐ node2string($options, $node, $vert_dashes)
Returns a string of the node's name and attributes, with a leading indent, suitable for printing.
Possible keys in the $options hashref:
no_attributes => $Booleanโ if it's1, the node's attributes are not included in the string that comes back.
Default: 0 (include attributes).
Ignore the parameter $vert_dashes. The code just uses it as temporary storage. ๐
Calls format_node($options, $node).
Called by tree2string($options, [$some_tree]).
๐ฆ quote_name($name)
Returns the string "'$name'", which is used in various methods for outputting node names. โจ
๐ฅ random_network($options)
This method can be called as a class method or as an object method.
First case: it constructs a randomly arranged network under a new node, and returns the root node of that tree. Latter case: it constructs the network under $node.
ngl, right now this is implemented a bit half-heartedly, and half-wittedly ๐ I basically needed to make up random-looking networks to stress-test all the various tree-dumper methods, so I wrote this. If you actually want to rely on this for any application more serious than that, I suggest you examine the source code and see if it really does what you need (say, in terms of reliability of randomness); and feel totally free to suggest changes to me (especially in the form of "I rewrote random_network($options), here's the code...").
It takes four options:
max_node_countโ maximum number of nodes this tree will be allowed to have (counting the root) Default:25.min_depthโ minimum depth for the tree Leaves can be generated only after this depth is reached, so the tree will be at least this deep โ unlessmax_node_countis hit first.
Default: 2.
max_depthโ maximum depth for the tree The tree will not be deeper than this.
Default: 3 plus min_depth.
max_childrenโ maximum number of children any mother in the tree can have. Default:4.
๐ read_attributes($s)
Parses the string $s and extracts the name and attributes, assuming the format is as generated by tree2string($options, [$some_tree]).
This bascially means the attribute string was generated by hashref2string($hashref).
Attributes may be absent, in which case they default to {}.
Returns a new node with this name and these attributes.
This method is for use by read_tree($file_name).
See t/tree.without.attributes.txt and t/tree.with.attributes.txt for sample data. ๐ธ
โญ read_tree($file_name)
Returns the root of the tree read from $file_name.
Like, the file has to have been made by redirecting the output of tree2string($options, [$some_tree]) into a file, no cap โ because this method is out here making assumptions about the format of the stringified attributes. ๐
So don't hand it just anything, bestie.
read_tree() works with utf-8 data. See t/read.tree.t and t/tree.utf8.attributes.txt. ๐ง
Note (and this one matters): to call this method you need a caller. It'll be a tree of 1 node. The reason is that inside this method it calls various other methods, and for those calls it needs$self. That way, those methods can be called from anywhere, and not just from withinread_tree(). ikyk ๐ซถ
For reading and writing trees to databases, see Tree::DAG_Node::Persist. Different slay, different page.
Also, it calls string2hashref($s). Noted. โจ
remove_daughter(LIST)
An exact synonym for remove_daughters(LIST). That's the whole thing. Same method, different name. ๐
remove_daughters(LIST)
This one removes the nodes listed in LIST from $mother's daughter list. If LIST is empty, it's a no-operation, so don't worry, nothing happens. And if there are things in LIST that aren't a current daughter of $mother? They get ignored. ๐ซ
Not to be confused with clear_daughters(). Sisters, not twins. ๐
replace_with(LIST)
This replaces $node in its mother's daughter list โ it unlinks $node and swaps in the items from LIST. ๐ซ It returns a list made of $node followed by LIST, i.e., the nodes that replaced it.
LIST can include $node itself (presumably at most once, bestie โ let's stay sane). LIST can also be the empty list. However, if any items in LIST are sisters to $node, they are ignored, and they will not be in the copy of LIST passed as the return value. ๐
As you might expect for any linking operation, the items in LIST cannot be $node's mother, or any ancestor to it. And the items in LIST are, of course, unlinked from their mothers (if they have any) as they're linked to $node's mother.
(In the special (and, frankly, bizarre) case where $node is root, this simply calls $this->unlink_from_mother on all the items in LIST, making them roots of their own trees. main character energy, honestly. ๐)
Now listen โ the daughter-list of $node is not necessarily affected; and neither are the daughter-lists of the items in LIST. Bringing this up in case you think replace_with switches one node for another with respect to its mother list and its daughter list, leaving the rest of the tree unchanged. If that's the glow-up you want โ replacing $Old with $New โ then you want this instead:
$New->set_daughters($Old->clear_daughters);
$Old->replace_with($New);
(Can't sit here and say $node's and LIST-items' daughter lists are never affected by replace_with though โ they can be affected, in this case:
$N1 = ($node->daughters)[0]; # first daughter of $node
$N2 = ($N1->daughters)[0]; # first daughter of $N1;
$N3 = Tree::DAG_Node->random_network; # or whatever
$node->replace_with($N1, $N2, $N3);
As a side effect of attaching $N1 and $N2 to $node's mother, they're unlinked from their parents ($node, and $N1, respectively). But N3's daughter list is unaffected.)
In other words: this method does what it has to, as you'd expect it to. ๐ periodt.
replace_with_daughters()
This replaces $node in its mother's daughter list, by unlinking $node and replacing it with its daughters ๐ฆ. In other words, $node becomes motherless and daughterless as its daughters move up and take its place. It returns a list consisting of $node followed by the nodes that were its daughters.
In the special (and bizarre) case where $node is root, this simply unlinks its daughters from it, making them roots of their own trees.
Effectively the same as $node->replace_with($node->daughters), but more efficient, since less checking has to be done. (And also โ lowkey โ $node->replace_with_daughters is a more common operation in tree-wrangling than $node->replace_with(LIST), so it deserves a named method of its own, but that's just me. ๐)
โ ๏ธ Heads up though: if you radically change 'mother'/'daughters' bookkeeping, you may have to change this routine, since it's one of the places that directly writes to 'daughters' and 'mother'. It's giving very low-level. ๐ง
right_sister()
Returns the node that's the immediate right sister of $node. If $node is the rightmost (or only) daughter of its mother (or has no mother at all), then this returns undef. ๐ญ
See also add_left_sisters(LIST) and add_right_sisters(LIST).
right_sisters()
Returns a list of nodes that're sisters to the right of $node. If $node is the rightmost (or only) daughter of its mother (or has no mother), then you get an empty list back. ๐ซฅ
See also add_left_sisters(LIST) and add_right_sisters(LIST).
root()
Returns the root of whatever tree $node is a member of ๐ณ. And if $node is the root, then the result is $node itself. Cute, honestly.
Not to be confused with is_root(). Whole different vibe.
self_and_descendants()
Returns a list consisting of itself (as element 0) and all the descendants of $node ๐. Returns just itself if $node is a terminal_node. Baby tree, baby list.
(And note that it's spelled "descendants", not "descendents". We spell it correctly in here. โจ)
self_and_sisters()
Returns a list of all nodes (going left-to-right) that have the same mother as $node โ including $node itself. This is just like $node->mother->daughters, except that that fails where $node is root, whereas $root->self_and_siblings, as a special case, returns $root. ๐ซถ
(And contrary to how you may interpret how this method is named, "self" is not (necessarily) the first element of what's returned. Plot twist. ๐ญ)
set_daughters(LIST)
This unlinks all of $mother's daughters, and replaces them with the daughters in LIST. ๐
Currently implemented as just $mother->clear_daughters followed by $mother->add_daughters(LIST). That is literally the entire method.
simple_lol_to_tree($simple_lol)
โ ๏ธ This must be called as a class method.
This is like lol_to_tree, except that rule 1 doesn't apply โ i.e., all scalars (or really, anything not a listref) in the LoL-structure end up as named terminal nodes, and only terminal nodes get names (and, of course, that name comes from that scalar value). Super useful for making things like expression trees, or at least starting them off. Consider that this:
$tree = Tree::DAG_Node->simple_lol_to_tree(
[ 'foo', ['bar', ['baz'], 'quux'], 'zaz', 'pati' ]
);
converts from something like a Lispish or Iconish tree โ if you pretend the brackets are parentheses. ๐ง ๐ซ
And note that there is a (possibly surprising) degenerate case of what's being called a "simple-LoL", and it goes like this:
$tree = Tree::DAG_Node->simple_lol_to_tree('Lonely');
This is the (only) way you can specify a tree consisting of only a single node, which here gets the name 'Lonely'. ๐ฅบ iconic.
sisters()
Returns a list of all nodes (going left-to-right) that have the same mother as $node โ not including $node itself. If $node is root, this returns the empty-list. ๐
string2hashref($s)
Returns the hashref built from the string.
The string is expected to be something like '{AutoCommit => '1', PrintError => "0", ReportError => 1}'.
The empty string is returned as {}. ๐ถ
Called by read_tree($file_name).
tree_to_lol()
Returns that tree (starting at $node) represented as a LoL, like what $lol, above, holds. (This is as opposed to tree_to_lol_notation($options), which returns the viewable code like what gets evaluated and stored in $lol, above. Two very different formats, bestie, don't mix them up.)
Undefined node names are returned as the string 'undef'. ๐
See also decode_lol($lol).
Lord only knows what you use this for โ maybe for feeding to Data::Dumper, in case tree_to_lol_notation($options) doesn't do just what you want? ๐
tree_to_lol_notation($options)
Dumps a tree (starting at $node) as the sort of LoL-like bracket notation you see in the example code above. Returns just one big block of text. The only option is multiline โ if true, it dumps the text as the sort of indented structure as seen above; if false (and it defaults to false), it dumps it all on one line (with no indenting, of course). ๐
For example, starting with the tree from the above example, this:
print $tree->tree_to_lol_notation, "\n";
prints the following (which has been broken over two lines for sake of printability of documentation):
[[[['Det:The'], [['dog'], 'N'], 'NP'], [["/with rabies\x5c"],
'PP'], 'NP'], [['died'], 'VP'], 'S'],
And doing this:
print $tree->tree_to_lol_notation({ multiline => 1 });
prints the same content, just spread over many lines, and prettily indented. โจ
Undefined node names are returned as the string 'undef'.
tree_to_simple_lol()
Returns that tree (starting at $node) represented as a simple-LoL โ i.e., one where non-terminal nodes are represented as listrefs, and terminal nodes are gotten from the contents of those nodes' name attributes. ๐ฆ
Note that in the case of $node being terminal, what you get back is the same as $node->name.
Compare to tree_to_simple_lol_notation.
Undefined node names are returned as the string 'undef'.
See also decode_lol($lol).
tree_to_simple_lol_notation($options)
A simple-LoL version of tree_to_lol_notation (which see); it takes the same options. ๐
Undefined node names are returned as the string 'undef'.
tree2string($options, [$some_tree])
Okay so, here, the [] represent an optional parameter ๐.
Returns an arrayref of lines, suitable for printing.
Draws a nice ASCII-art representation of the tree structure ๐.
The tree looks like:
Root. Attributes: {}
|--- ร. Attributes: {# => "รร"}
| |--- รข. Attributes: {# => "รขรข"}
| | |--- ร. Attributes: {# => "รร"}
| |--- รค. Attributes: {# => "รครค"}
| |--- รฉ. Attributes: {# => "รฉรฉ"}
| |--- ร. Attributes: {# => "รร"}
| |--- รฑ. Attributes: {# => "รฑรฑ"}
| |--- ร. Attributes: {# => "รร"}
| |--- รด. Attributes: {# => "รดรด"}
| |--- รด. Attributes: {# => "รดรด"}
|--- ร. Attributes: {# => "รร"}
|--- ยฎ. Attributes: {# => "ยฎยฎ"}
| |--- ยฉ. Attributes: {# => "ยฉยฉ"}
|--- ยฃ. Attributes: {# => "ยฃยฃ"}
|--- โฌ. Attributes: {# => "โฌโฌ"}
|--- โ. Attributes: {# => "โโ"}
|--- รxX. Attributes: {# => "รxXรxX"}
|--- รญ. Attributes: {# => "รญรญ"}
|--- รบ. Attributes: {# => "รบรบ"}
|--- ยซ. Attributes: {# => "ยซยซ"}
|--- ยป. Attributes: {# => "ยปยป"}
Or, without attributes, it's giving this: โจ
Root
|--- ร
| |--- รข
| | |--- ร
| |--- รค
| |--- รฉ
| |--- ร
| |--- รฑ
| |--- ร
| |--- รด
| |--- รด
|--- ร
|--- ยฎ
| |--- ยฉ
|--- ยฃ
|--- โฌ
|--- โ
|--- รxX
|--- รญ
|--- รบ
|--- ยซ
|--- ยป
Like, look at that ๐ญ it's gorgeous. Unicode city. Anyway โ go peek at scripts/cut.and.paste.subtrees.pl for the behind-the-scenes of cutting and pasting subtrees, iykyk ๐
Example usage, bestie:
print map("$_\n", @{$tree->tree2string});
Ok so you can call this with $some_tree set to any $node, and it'll print the tree like that node is the root ๐ณโจ
And if you simply cannot be bothered to supply options, hit it with tree2string({}, $node).
Possible keys in the $options hashref (which defaults to {}):
no_attributes => $Booleanโ set it to 1 and the node's attributes do NOT make it into the string you get back. Default: 0 (attributes are in, bestie, they're included).
It calls node2string($options, $node, $vert_dashes).
Also go look at draw_ascii_tree([$options]) ๐
๐ธ unlink_from_mother() ๐ธ
This one removes the node from the daughter list of its mother. And if it has no mother at all? No-op, nothing happens, calm down ๐
Returns the mother it got unlinked from (if there was one, that is).
๐ฆ walk_down($options) ๐ฆ
This does a depth-first traversal of the whole structure at and under $node. What actually goes down at each node depends on the options hashref โ which you must provide, no skipping. There are three options: callback and callbackback (you need at least one of these defined, and as a sub reference, fr fr), plus _depth.
Here's what walk_down() does, pseudocode style:
- Starting point โ start at the $node you were given.
- Callback โ if there's a callback, call it with $node as the first argument and the options hashref as the second argument (which has that potentially-useful
_depthin it, remember that). This function must return true or false โ if it comes back false, it blocks the next step: - Daughters โ if $node has any daughter nodes, increment
_depth, and call$daughter->walk_down($options)for each daughter (in order, obviously, we're not monsters), where options_hashref is the same hashref it was called with. When that returns, decrement_depth. - Callbackback โ if there's a callbackback, call just it, exactly like callback (but toss the return value out, it doesn't count). Note that callback returning false blocks traversal below $node, but it does NOT block calling callbackback for $node. (And like, in the super unlikely case that $node has stopped being a node object, callbackback won't get called at all.)
- Return ๐ซถ
$node->walk_down($options) is the way to recursively do stuff to a tree (if you start at the root) or to part of a tree. If what you're doing is best done via pre-pre order traversal, use callback; if what you're doing is best done with post-order traversal, use callbackback. walk_down() is literally the basis for so many of the methods in this class. Go read the source code for examples, both the simple ones and the absolutely horrific ones ๐
Note that if you don't specify _depth, it effectively defaults to 0. Set it to scalar($node->ancestors) if you want _depth to reflect the true depth-in-the-tree for the nodes called, instead of just the depth below $node. (If $node is the root, there's no difference, duh.)
And btw, it is a BAD IDEA to modify the tree from inside the callback. Unpredictable things may happen, and not the fun kind. The author instead suggests having your callback add to a stack of things that need changing, and then, once walk_down() is completely finished, changing those nodes from that stack. Do that. Please. ๐
And note that the existence of walk_down() does not mean you can't write your own special-use traversers, bestie โ go off ๐
๐ WHEN AND HOW TO DESTROY THE TREE ๐
So it should be obvious by now: if you've built a big parse tree or whatever, and you're done with it, you should call $some_node->delete_tree on it if you want that memory back. Free the RAM, queen.
But consider this case โ you've got this tree:
A
/ | \
B C D
| | \
E X Y
Let's say you decide you don't want D or any of its descendants in the tree anymore, so you call D->unlink_from_mother. This does NOT automagically destroy the tree D-X-Y. Nope. It merely splits the tree into two:
A D
/ \ / \
B C X Y
|
E
To actually destroy D and its little mini tree, you have to explicitly call delete_tree on it. No shortcuts, sorry ๐ญ
Note, however, that if you call C->unlink_from_mother, and you don't have a link to C anywhere, then it does magically go away. Why? Because nothing links to C โ whereas with the D-X-Y tree, D links to X and Y, and X and Y each link back to D. So one of those keeps itself alive and the other just floats off into the void ๐ฆ
Also note that calling C->delete_tree is harmless โ after all, a tree of only one node is still a tree. Periodt.
So this is a surefire way of getting rid of all $node's children and freeing up the memory associated with them and their descendants:
foreach my $it ($node->clear_daughters) { $it->delete_tree }
Just be sure not to do this:
foreach my $it ($node->daughters) { $it->delete_tree }
$node->clear_daughters;
That's bad. That's so bad. The first call to $_->delete_tree will climb to the root of $node's tree and nuke the WHOLE tree, not just the bits under $node. Like, you might as well have just called $node->delete_tree and saved yourself the drama. (And moreover, once $node is dead, you can't call clear_daughters on it, so you'll get an error there too. Double L.) ๐
๐ BUG REPORTS ๐
If you find a bug in this library, report it to the author as soon as possible, at the address listed in the MAINTAINER section, below. Please try to be as specific as possible about how you got the bug to occur, so they can actually reproduce it and not just stare at your message like ๐๏ธ๐๐๏ธ
๐ซถ HELP! ๐ซถ
If you develop a given routine for dealing with trees in some way, and use it a lot, then if you think it'd be of use to anyone else, do email the author about it โ it might be helpful to others to include that routine, or something based on it, in a later version of this module. Sharing is caring, bestie โจ
It's also occurred to the author that you might like to (and might yourself develop routines to) draw trees in something other than ASCII art. If you do โ say, for PostScript output, or for output interpretable by some external plotting program โ they'd be most interested in the results. Show off your art, you know you want to ๐จ
๐ RAMBLINGS ๐
This module uses strict, but it was never written with -w warnings in mind โ so if you use -w, do not be surprised if you see complaints coming from the guts of DAG_Node. And as long as there's no way to turn off -w for a given module (instead of having to do it in every single subroutine with a local $^W), that's not getting changed. However, the author does, at points, get bursts of ambition, and tries to fix code in DAG_Node that generates warnings, as they come across them โ which is only occasionally. Feel free to email any patches for any such fixes you come up with, tho ๐
Currently, nothing is assumed (or enforced) about the class membership of nodes being manipulated, other than by testing whether each one provides a method is_node(), a la:
die "Not a node!!!" unless UNIVERSAL::can($node, "is_node");
So as far as the author's concerned, a given tree's nodes are free to belong to different classes, just so long as they provide/inherit is_node(), the few methods that this class relies on to navigate the tree, and have the same internal object structure, or a superset of it. Presumably that'd be the case for any object belonging to a class derived from Tree::DAG_Node, or belonging to Tree::DAG_Node itself. Go wild with your inheritance, it's fine ๐
When routines in this class access a node's mother attribute, or its daughters attribute, they (generally) do so directly (via $node->{'mother'}, etc.), for sake of efficiency. But classes derived from this class should probably do this thru a method instead (via $node->mother, etc.), for sake of portability, abstraction, and general goodness. Good vibes only ๐ธ
However, no routines in this class (aside from, necessarily, _init(), _init_name(), and name()) access the name attribute directly; routines (like the various tree draw/dump methods) get the name value thru a call to $obj->name(). So if you want the object's name to not be a real attribute, but instead have it derived dynamically from some feature of the object (say, based on some of its other attributes, or based on its address), you can override the name() method, without causing problems. (Be sure to consider the case of $obj->name as a write method, as it's used in lol_to_tree($lol) and random_network($options).) Main character energy ๐
โ FAQ โ
๐ Which is the best tree processing module?
Tree::DAG_Node, as it happens. More details: SEE ALSO.
๐ How to process every node in tree?
See walk_down($options). $options normally looks like this, assuming we wish to pass in an arrayref as a stack:
my(@stack);
$tree -> walk_down
({
callback =>
sub
{
my($node, $options) = @_;
# Process $node, using $options...
push @{$$options{stack} }, $node -> name;
return 1; # Keep walking.
},
_depth => 0,
stack => \@stack,
});
# Process @stack...
๐ How do I switch from Tree to Tree::DAG_Node?
- The node's name โ in
Treeyou use$node -> valueand inTree::DAG_Nodeit's$node -> name. - The node's attributes โ in
Treeyou use$node -> metaand inTree::DAG_Nodeit's$node -> attributes.
๐ Are there techniques for processing lists of nodes?
- Copy the daughter list, and change it
@them = $mother->daughters;
@removed = splice(@them, 0, 2, @new_nodes);
$mother->set_daughters(@them);
- Select a sub-set of nodes
$mother->set_daughters
(
grep($_->name =~ /wanted/, $mother->daughters)
);
๐ Why did you break up the sections of methods in the POD?
Because the author wants to list the methods in alphabetical order. Organized queen ๐
๐ Why did you move the POD to the end?
Because the apostrophes in the text confused the syntax highlighter in the author's editor UltraEdit. ๐ editor beef, honestly
โจ SEE ALSO โจ
HTML::Element,HTML::TreeandHTML::TreeBuilderโ Sean is also the author of these modules.Treeโ Lightweight.Tree::Binaryโ Lightweight.Tree::DAG_Node::Persistโ Lightweight.Tree::Persistโ Lightweight.Forestโ Uses Moose.
Tree::DAG_Node itself is also lightweight. Skinny legend ๐ฆ
๐ธ REFERENCES ๐ธ
okay bestie, if you wanna go deeper into tree lore (and honestly, iconic behavior), here's the reading list:
- Wirth, Niklaus. 1976. Algorithms + Data Structures = Programs Prentice-Hall, Englewood Cliffs, NJ.
- Knuth, Donald Ervin. 1997. Art of Computer Programming, Volume 1, Third Edition: Fundamental Algorithms. Addison-Wesley, Reading, MA.
Wirth's classic โ which is, like, currently and lamentably out of print, we're all grieving ๐ โ has a genuinely good section on trees. Our author finds it clearer than Knuth's (not quite as encyclopedic tho), probably because Wirth's example code is in a block-structured high-level language (basically Pascal), instead of in assembler (MIX). no cap, reading Pascal > reading MIX, that's just facts.
Until some kind publisher brings out a new printing of Wirth's book, try poking around used bookstores (or www.abebooks.com) for a copy. The author thinks it was also republished in the 1980s under the title Algorithms and Data Structures, and in a German edition called Algorithmen und Datenstrukturen. (That is โ they're sure books by Knuth were published under those titles, but they're assuming those are just later printings/editions of Algorithms + Data Structures = Programs.) so like, go forth and thrift ๐ซถ
๐ MACHINE-READABLE CHANGE LOG ๐
The file Changes was converted into Changelog.ini by Module::Metadata::Changes. periodt.
๐ REPOSITORY ๐
<https://github.com/ronsavage/Tree-DAG_Node>
๐ SUPPORT ๐
Email the author, or log a bug on RT:
<https://github.com/ronsavage/Tree-DAG_Node/issues>.
we don't gatekeep bugs here ๐
๐ ACKNOWLEDGEMENTS ๐
The code to print the tree, in tree2string(), was adapted from Forest::Tree::Writer::ASCIIWithBranches by the dread Stevan Little. shoutout, king ๐
๐ MAINTAINER ๐
- David Hand,
<cogent@cpan.org>up to V 1.06. - Ron Savage
<rsavage@cpan.org>from V 1.07.
In this POD, usage of 'I' refers to Sean, up until V 1.07. (yes bestie, the "I" changes hands mid-document, it's giving relay race ๐)
โจ AUTHOR โจ
Sean M. Burke, <sburke@cpan.org>
โ๏ธ COPYRIGHT, LICENSE, AND DISCLAIMER โ๏ธ
Copyright 1998-2001, 2004, 2007 by Sean M. Burke and David Hand.
See the accompanying LICENSE file.
This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. (i.e. it's free, it's hopeful, it promises nothing legally โ the classic cpan vibe ๐ญ)
and that's a wrap on the whole page, bestie!! ๐ you've officially read the entire Tree::DAG_Node manual and you ate, no crumbs. this is perl v5.40.1, dated 2026-07-05, and it's Tree::DAG_Node(3pm) โ go build some tree structures, you absolute main character ๐๐ฆ๐
๐ฆ where this page comes from
- ubuntu stonking: libtree-dagnode-perl 1.37-1 ยท Artistic-1.0-Perl OR GPL-1.0-or-later
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms