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

I built a yahoo style link managing system.
I need to be able to print a dropdown box with the (sorted) directory tree. I am using BerkeleyDB.pm as the database, i came up with some code that works but its ptretty ugly -- hopefully somebody could help me make it more elegant.
sub BuildCatList { my @cat_list = (); my $cats = &main::GetDB('cats'); # a ref to a tied BerkeleyDB hash local *get_next_lvl = sub { my ($cat_id, $depth) = @_; my %cache = (); my $cat_info = Category->new($cat_id); defined $cat_info or warn "no such cat '$cat_id'" && return undef; foreach my $id (@{$cat_info->field('sub_cats')}) { my $subcat_info = Category->new($id); defined $subcat_info or warn "no such cat '$id'" && next; $cache{$id} = $subcat_info->field('name'); } foreach my $id (sort {$cache{$a} cmp $cache{$b}} keys %cache) { push (@cat_list, { depth => $depth , id => $id , name => '. . ' x $depth . $cache{$id} }); defined get_next_lvl($id, $depth+1) or warn "undef ret" && return undef; } return 1; }; push(@cat_list, {id => 'top', name => 'Top', depth => 0} ); defined get_next_lvl('top', 1) or warn "undef ret" && return undef; $cats->{'~cat_list'} = &main::nfreeze( \@cat_list ); return 1; }
$cats->{'~cat_list'} is then passed into HTML::Template.

----------------------------------
Here's some of Google's Wizdom (Google Glossary):
Stupid: Losing $25 on the game and $25 on the instant replay.

Replies are listed 'Best First'.
Re: Printing a dir tree
by dpuu (Chaplain) on Jul 21, 2002 at 03:02 UTC
    I agree that it looks pretty ugly. But to determine the appropriate simplifications, I really need to see what the class Category is; and also what main::nfreeze is doing. --Dave
      main::nfreeze is calling the nfreeze from Storable.pm (for later retrieval).
      Category is basically a hash of properties (eg. name, description, sub categories, etc.)

      Here’s another view of the problem:

      given: $hash{'root'} = ['a', 'b']; $hash{'a'} = ['c'];
      I need an array that looks like this: [0] root [1] root / a [2] root / a / c [3] root / b
      (of course $hash can be much larger).

      ----------------------------------
      Here's some of Google's Wizdom (Google Glossary)
      Stupid: Losing $25 on the game and $25 on the instant replay.

        OK, I'm going to assume that I can modify your Category class. If not, you can simply derive a new class from it, and place my additions in that.

        First, lets stop using that nasty "field" method. We can simply define:

        sub name { shift->field('name') } sub sub_cat_ids { @{shift->field('sub_cats') }
        That'll get rid of a bit of the line noise.

        Next, I want a more powerful 'new' operator. To avoid confusion, I'll name it new_tree. Later, we may rename it back to "new":

        sub new_tree { my $class = shift; my $id = shift; my $depth = shift; my $self = $class->new($id); $self->{_depth} = $depth; $self->{_sub_cats} = [ sort { $a->name cmp $b->name } map { $class->new_tree($_, $depth+1) } $self->sub_cat_ids ]; return $self; } sub sub_cats { @{shift->{_sub_cats} }
        With these methods in place, we can add one final method to return the flattened list:
        sub as_hash { my $self = shift; my $name = $self->name; my $depth = $self->depth; my $id = $self->id; return { depth->$depth, id=>$id, name=>$name }; } sub as_flattened_list { my $self=shift; return $self->as_hash, map { $_->as_flattened_list } $self->sub_cats }
        Finally, we need to modify your BuildCatList function to use the upgraded Category class:
        sub BuildCatList { my $top = Category->new_tree("top", 0); my @cats_list = $top->as_flattened_list; my $cats = &main::GetDB('cats'); $cats->{'~cat_list'} = &main::nfreeze( \@cat_list ); }
        I've omitted all your undef checks; you can add them back if you want. --Dave.