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

Fellow Monasterians,

I'm building a web page hierarchy 'tree' from a db table that conveniently stores the relationships between the pages (allows users to see the site make up), I've gotten it to work, but the code looks crazy bloated. Any way to golf this and make it shorter? faster? cleaner? but still readable? Thanks!

my $sqldata = [ { pageid => 1, level => 1, under => 0, pagename => 'Groups' }, { pageid => 2, level => 1, under => 0, pagename => 'About' }, { pageid => 5, level => 3, under => 4, pagename => 'Tea' }, { pageid => 4, level => 2, under => 1, pagename => 'Womens' }, { pageid => 6, level => 4, under => 5, pagename => 'Registration' } +, { pageid => 7, level => 2, under => 1, pagename => 'Mens' }, { pageid => 8, level => 3, under => 4, pagename => 'Retreat' } ]; #level = hierarchy, #under = page that this page falls under my @tree; for my $i ( 0 .. $#$sqldata ) { if ($sqldata->[$i]{under} == 0 ) { push (@tree, $sqldata->[$i]); } for my $j ( 0 .. $#$sqldata ) { if ($sqldata->[$j]{under} == $sqldata->[$i]{pageid} && $sqldata- +>[$j]{level} == 2) { $sqldata->[$j]{tab} = 1; push (@tree, $sqldata->[$j]); for my $k ( 0 .. $#$sqldata ) { if ($sqldata->[$k]{under} == $sqldata->[$j]{pageid} && $sq +ldata->[$k]{level} == 3) { $sqldata->[$k]{tab} = 2; push (@tree, $sqldata->[$k]); for my $l ( 0 .. $#$sqldata ) { if ($sqldata->[$l]{under} == $sqldata->[$k]{pageid} +&& $sqldata->[$l]{level} == 4) { $sqldata->[$l]{tab} = 3; push (@tree, $sqldata->[$l]); } } } } } } } for ( 0 .. $#tree ) { if ( $tree[$_]{tab} == 1 ) { print "---" } if ( $tree[$_]{tab} == 2 ) { print "------" } if ( $tree[$_]{tab} == 3 ) { print "---------" } print $tree[$_]{pagename}."<br />"; } OUTPUT: Groups ---Womens ------Tea ---------Registration ------Retreat ---Mens About

Update: fixed typos


—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot

Replies are listed 'Best First'.
Re: Cleaner code to build hierarchical display of page names
by BrowserUk (Patriarch) on May 17, 2005 at 06:37 UTC

    Here's a recursive solution that avoids arbitrary nesting limits. By passing a sub to the traverse routine you can make it do things other than just printing.

    $_ is set to the hashref for each page.

    Updated: Slightly better coding.

    #! perl -slw use strict; sub traverse { my( $ref, $under, $level, $code ) = @_; for my $node ( grep{ $_->{level} == $level && $_->{under} == $under } @$ref ) { local $_ = $node; $code->(); traverse( $ref, $node->{pageid}, $level+1, $code ); } } my $sqldata = [ { pageid => 1, level => 1, under => 0, pagename => 'Groups' }, { pageid => 2, level => 1, under => 0, pagename => 'About' }, { pageid => 5, level => 3, under => 4, pagename => 'Tea' }, { pageid => 4, level => 2, under => 1, pagename => 'Womens' }, { pageid => 6, level => 4, under => 5, pagename => 'Registration' } +, { pageid => 7, level => 2, under => 1, pagename => 'Mens' }, { pageid => 8, level => 3, under => 4, pagename => 'Retreat' } ]; traverse $sqldata, 0, 1, sub { print '---' x ( $_->{level} - 1 ), $_->{pagename}, '<br/>'; }; __END__ P:\test>457652 Groups<br/> ---Womens<br/> ------Tea<br/> ---------Registration<br/> ------Retreat<br/> ---Mens<br/> About<br/>

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: Cleaner code to build hierarchical display of page names
by mrborisguy (Hermit) on May 17, 2005 at 03:33 UTC
    wow... this is impressive, i like it!

    here's some thoughts, i don't think they're that great, but some things for you to think about.
    for my $i ( 0 .. $#$sqldata ) { if ($sqldata->[$i]{under} == 0 ) { push (@tree, $sqldata->[$i]); } for my $j ( 0 .. $#$sqldata ) { if ($sqldata->[$j]{under} == $sqldata->[$i]{pageid} && $sqldata- +>[$j]{level} == 2) { $sqldata->[$j]{tab} = 1; push (@tree, $sqldata->[$j]); for my $k ( 0 .. $#$sqldata ) { if ($sqldata->[$k]{under} == $sqldata->[$j]{pageid} && $sq +ldata->[$k]{level} == 3) { $sqldata->[$k]{tab} = 2; push (@tree, $sqldata->[$k]); for my $l ( 0 .. $#$sqldata ) { if ($sqldata->[$l]{under} == $sqldata->[$k]{pageid} +&& $sqldata->[$l]{level} == 4) { $sqldata->[$l]{tab} = 3; push (@tree, $sqldata->[$l]); } } } } } } }
    i didn't look at this too much, but it looks like you do the same thing a bunch of times nested. this may be a prime place for some recursion!



    for ( 0 .. $#tree ) { if ( $tree[$_]{tab} == 1 ) { print "---" } if ( $tree[$_]{tab} == 2 ) { print "------" } if ( $tree[$_]{tab} == 3 ) { print "---------" } print $tree[$_]{pagename}."<br />"; }
    can be changed to:
    for ( 0 .. $#tree ) { print "---" x $tree[$_]{tab}; print $tree[$_]{pagename]."<br />"; }


    i like this program, i'm very impressed. i would say with a little recursion and a little abstraction on the second part (instead of testing each value and printing the number of lines according to the test) you should be able to make this so you can have more than just 3 or 4 levels too.

      Yeah, I apologize for my print routine, which I just threw together to test my results. I did wonder how to use the tab value to avoid the if's. Didn't realize you could multiply a print statement! Ahhhhh, Perl!


      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
        That's no multiplication. It's a repition.

        From perlop:
        Binary ``x'' is the repetition operator. In scalar context or if the left operand is not enclosed in parentheses, it returns a string consisting of the left operand repeated the number of times specified by the right operand. In list context, if the left operand is enclosed in parentheses, it repeats the list.
        # print row of dashes print '-' x 80; #tab over print "\t" x ($tab/8), ' ' x ($tab%8); # a list of 80 1's @ones = (1) x 80; # set all elements to 5 @ones = (5) x @ones;
        Update: Fixed formatting


        holli, /regexed monk/
Re: Cleaner code to build hierarchical display of page names
by dragonchild (Archbishop) on May 17, 2005 at 03:30 UTC
    Reorganize your datastructure to reflect your needs. Alternately, use Algorithm::Loops, particularly the NestedLoops() function.

    • In general, if you think something isn't in Perl, try it out, because it usually is. :-)
    • "What is the sound of Perl? Is it not the sound of a wall that people have stopped banging their heads against?"
Re: Cleaner code to build hierarchical display of page names
by jhourcle (Prior) on May 17, 2005 at 11:26 UTC

    This probably won't help most people, but if you're using Oracle, they support CONNECT BY which is made specifically to do this sort of thing.

    SELECT lpad('-', 3*(level-1)) || pagename FROM tablename START WITH under=0 CONNECT BY PRIOR pageid = undef;
Re: Cleaner code to build hierarchical display of page names
by danb (Friar) on May 17, 2005 at 15:08 UTC

    This isn't really your question, but I recommend reading up on many of the design patterns for storing and accessing trees in databases. Here's a great start:

    Hierarchical data in RDBMSs

    Many other designs are much faster than the parent/child design. But that may not be important in your case.

    -Dan