bryank has asked for the wisdom of the Perl Monks concerning the following question:

Hi all. Sorry for yet another tree question. I've been trying to understand all the various examples you have already sent me.

What I have been trying to do this morning is understand how the Tree::Simple module works specifically. I want to take an input file that contains a delimited file of child/parents, build a tree, and output a flattened version.

If I had:

apple:fruit granny smith:apple rotten granny smith:granny smith . . . orange|fruit
the output would be:

Fruit:Apple:Granny Smith:Rotten Granny Smith Fruit:Orange

The example on the module page for Tree::Simple is helpful, but I am trying to figure out how to read in my input file. I am assuming I'd use a while loop. Any tips on tweaking the below code?

use warnings; use strict; use Tree::Simple; # make a tree root my $tree = Tree::Simple->new("0", Tree::Simple->ROOT); # explicity add a child to it $tree->addChild(Tree::Simple->new("1")); # specify the parent when creating # an instance and it adds the child implicity my $sub_tree = Tree::Simple->new("2", $tree); # chain method calls $tree->getChild(0)->addChild(Tree::Simple->new("1.1")); # add more than one child at a time $sub_tree->addChildren( Tree::Simple->new("2.1"), Tree::Simple->new("2.2") ); # add siblings $sub_tree->addSibling(Tree::Simple->new("3")); # insert children a specified index $sub_tree->insertChild(1, Tree::Simple->new("2.1a")); # clean up circular references $tree->DESTROY(); $tree->traverse(sub { my ($_tree) = @_; print (("\t" x $_tree->getDepth()), $_tree->getNodeValue(), "\n"); });

Here is what I have so far:

use warnings; use strict; use Tree::Simple; my $tree = Tree::Simple->new("0", Tree::Simple->ROOT); while(<DATA>) { chomp; my ($child, $parent) = split ":"; my $sub_tree = Tree::Simple->new("$parent", $tree); $sub_tree->addChild(Tree::Simple->new("$child")); $tree->DESTROY(); } $tree->traverse(sub { my ($_tree) = @_; print (("\t" x $_tree->getDepth()), $_tree->getNodeValue(), "\n"); }); __DATA__ apple:fruit granny smith:apple rotten granny smith:granny smith orange:fruit
Output:

fruit apple apple granny smith granny smith rotten granny smith fruit orange

Replies are listed 'Best First'.
Re: Using Tree::Simple with while loop and input file.
by mzedeler (Pilgrim) on Jun 30, 2009 at 22:43 UTC

    Tree::Simple may be a good module for certain specialized tasks, but you can create and manipulate all the trees you like by using plain hash references. It's straightforward and much more readable for other perl developers.

    Creating a tree from a file can be easy as:

    use strict; use warnings; my $lookup; while(my($parent, @children) = <DATA> =~ /(\S+)\s*/g) { $lookup->{$parent} ||= []; push @{$lookup->{$parent}}, @children; } sub build { my $children = delete $lookup->{+shift} || []; my $parent = {}; $parent->{$_} = build($_) for(@$children); return $parent; } my $tree = build('ROOT'); use Data::Dumper; print Dumper $tree; __DATA__ ROOT a b c b e f a d

    This code will build a nice tree of hashrefs and the input reading code nearly matches what you want. It'll even read the input regardless of how the lines are ordered.

    I found the following tutorials for you:

    Perl Hash Howto

    perlreftut

    and

    Hash tutorial

Re: Using Tree::Simple with while loop and input file.
by BioLion (Curate) on Jun 30, 2009 at 18:41 UTC

    I am still getting to grips with it too... but i find Data::TreeDumper very useful,
    I am sure a filter could be designed to print your output just as you want it.

    Hope this helps

    use warnings; use strict; use Tree::Simple; use Data::TreeDumper; my $tree; while(<DATA>) { chomp; my ($child, $parent) = split ":"; print "parent : $parent child : $child\n"; ## if it is the first parent we have seen if (! defined$tree){ warn "Creating new \$tree\n"; $tree = Tree::Simple->new("root")->addChild( Tree::Simple->new("$parent")->addChild( Tree::Simple->new("$child") ) ); } else { warn "\$tree is already growing so adding to it\n"; ## the tree is already growing so add the child to the appropria +te parent my @nodes = @{ $tree->getAllChildren() }; print "nodes: ".scalar@nodes."\n"; my %names = map{$_->getNodeValue() => $_} @nodes; print "".(join "\t", "Node Names : ", keys%names, "\n"); if (exists $names{$parent}){ warn "Adding child $child to parent $parent\n"; $names{$parent}->addChild( Tree::Simple->new("$child") ); } else { ## parent doesn't exist, so add it as a child to the root warn "Adding parent $parent and child $child to the root\n"; $tree->addChild( Tree::Simple->new("$parent")->addChild( Tree::Simple->new("$child") ) ); } } $tree->DESTROY(); } $tree->traverse(sub { my ($_tree) = @_; print (("\t" x $_tree->getDepth()), $_tree->getNodeValue(), "\n"); }); print DumpTree($tree, "tree", FILTER => \&filter, ); sub filter { ## ripped from the Data::TreeDumper docs my $s = shift ; if ('Tree::Simple' eq ref $s) { my $counter = 0 ; return ( 'ARRAY' , $s->{_children} , map{[$counter++, $_->{_n +ode}]} @{$s->{_children}} # index generation ) ; } return ( Data::TreeDumper::DefaultNodesToDisplay($s) ) ; } __DATA__ apple:fruit granny smith:apple rotten granny smith:granny smith orange:fruit
    Just a something something...
Re: Using Tree::Simple with while loop and input file.
by psini (Deacon) on Jun 30, 2009 at 21:36 UTC

    You really don't need Tree::Simple to do that. A simple approach could be:

    • Read the file in a hash in the form child => parent
    • Get all the leaves: keys of the hash that are not used also as values (keys, grep)
    • For each leaf
      • Generate an array with its "genealogy" searchin in the hash till you get a value that's not a key (a root)
      • reverse the array
      • join
      • print

    All considered it should be no more than 20 lines of code.

    Update: make it 22...

    #!/usr/bin/perl use strict; use warnings; open my $f,"<","x.txt"; my %tree; while (<$f>) { chomp; my ($c,$p)=split(/:/,$_); $tree{$c}=$p; } my %eert = reverse %tree; foreach my $l (keys %tree) { if (!defined($eert{$l})) { my @g=($l); while($l) { $l=$tree{$l}; push(@g,$l) if defined($l); } print join(':',@g),"\n"; } }

    Rule One: "Do not act incautiously when confronting a little bald wrinkly smiling man."

      You can cut the bottom loop by half (11 ⇒ 5) without making it more complex
      for (grep $tree{$_}, keys %tree) { my @g = $_; push @g, $_ while $_=$tree{$_}; print(join(':', @g), "\n"); }

      And why bother with an array?

      for (grep $tree{$_}, keys %tree) { my $g = $_; $g .= ":$_" while $_=$tree{$_}; print("$g\n"); }
Re: Using Tree::Simple with while loop and input file.
by ikegami (Patriarch) on Jun 30, 2009 at 19:32 UTC
      And what, exactly, do you see as the problem? OP clearly states that he is in a learning exercise (or are the words "I've been trying to understand all the various examples you have already sent me." somehow unclear to you). This kind of exploration, testing, and asking more questions should be encouraged, in my opinion. Starting a new thread may be less than optimal, but who cares (besides you, that is)?

      Chill (please). If you do not see an opportunity here to grovel for XP, you can always move on to another thread and try that one.

        And what, exactly, do you see as the problem?

        Ignoring all past answers leads to new questions going unanswered. The problem is that I don't think he wants that to happen.

        Starting a new thread may be less than optimal, but who cares (besides you, that is)?

        You are mistaken and putting words in my mouth. I don't care about the starting of new threads.

        Chill (please).

        What makes you think I'm not?

        If you do not see an opportunity here to grovel for XP

        A few more and I'll be able to buy a trip with them!