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

I have a subroutine which calls itself and I would like to have an array within it which is all of the keys within a nested array/hash I think I need it to remember the variable but I don't want to create a global variable. Here was my attempt. The hash looks something like this:
{ batchplot_output_filename => "gps3", plot_all_variables => 'off', # DEFAULT PLOTS & VALUES default_values => [ { profiles => [qw(prof1 prof2)], linewidth => ["2"], single_dotnotes => 'On', legend => ["On"], }, ], #BEGIN CUSTOM PLOTS custom_plots => [ [ { yvars => 'LATDIIP', xvars => 'ELONGI', }, ], ] }
sub analyse_user_inputs { my $hashORarray_ref = shift @_; my @the_inputs; foreach ( keys %{$hash_ref} ){ # print "$_ \n"; push(@the_inputs , $_); if (ref $hash_ref->{$_} eq 'HASH') { analyse_user_inputs($hash_ref->{$_}); } elsif(ref $hash_ref->{$_} eq 'ARRAY'){ foreach my $array_element (@{$hash_ref->{$_}}){ # if(ref $info eq 'ARRAY'){ # foreach my $element (@{$info}){ # analyse_user_inputs($element); # } # } # if (ref $info eq 'HASH') { analyse_user_inputs($array_element); # } } } } print Dumper(\@the_inputs); return(\@the_inputs); }

Replies are listed 'Best First'.
Re: subroutine memory variable scope
by NetWallah (Canon) on Dec 13, 2012 at 16:29 UTC
    You don't seem to be using @the_inputs for anything useful, and you throw away the returned value, at least in the internal recursive calls.

    Iterating over the inputs for a given recursion will take care of itself - I don't see a need to save the list for posterity.
    I would suggest getting rid of it entirely, since you have shown no use for it.

                 "By three methods we may learn wisdom: First, by reflection, which is noblest; Second, by imitation, which is easiest; and third by experience, which is the bitterest."           -Confucius

      Maybe I'm not understanding. I need the @the_inputs to be returned from the subfunction - I will be using it in a later call to verify if the user supplied inputs are valid ones.
Re: subroutine memory variable scope
by tobyink (Canon) on Dec 13, 2012 at 17:12 UTC

    You're maybe looking for something like this?

    use strict; use warnings; sub analyse_user_inputs { my $hash_ref = shift @_; my @the_inputs; foreach ( keys %{$hash_ref} ) { push @the_inputs, $_; if (ref $hash_ref->{$_} eq 'HASH') { push @the_inputs, @{ analyse_user_inputs($hash_ref->{$_}) +}; } elsif (ref $hash_ref->{$_} eq 'ARRAY') { foreach my $array_element (@{$hash_ref->{$_}}){ push @the_inputs, @{ analyse_user_inputs($array_elemen +t) }; } } } return(\@the_inputs); } my $data = { foo => 1, bar => [ { xyzzy => 'magic', } ], baz => { quux => 123, } }; use Data::Dumper; print Dumper(analyse_user_inputs($data));

    No need to play funny tricks with variable scoping. Just take the results of your recursive calls and push them onto @the_inputs.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      I think this is close - I made a few changes but I am still only getting the 1st tier of keys when I really want all sublevels of keys. I would like $user_inputs to be an array ref which contains:

      profiles, linewidth, single_dotnotes, legend, xvars, yvars

      when I try pushing the return of the subfunction I get a bunch of wierd empty returns

      function call

      my $user_inputs = analyse_user_inputs($Settings); print Dumper($user_inputs);

      incoming hash/array combo

      $Settings = { batchplot_output_filename => "gps3", plot_all_variables => 'off', # DEFAULT PLOTS & VALUES default_values => [ { profiles => [qw(prof1 prof2)], linewidth => ["2"], single_dotnotes => 'On', legend => ["On"], }, ], #BEGIN CUSTOM PLOTS custom_plots => [ [ { yvars => 'LATDIIP', xvars => 'ELONGI', }, ], ] }

      subfunction

      sub analyse_user_inputs { my $hashORarray_ref = shift @_; my @the_inputs; if (ref $hashORarray_ref eq 'HASH') { foreach ( keys %{$hashORarray_ref} ){ # print "$_ \n"; push @the_inputs , $_ ; analyse_user_inputs($hashORarray_ref->{$_}); } } if (ref $hashORarray_ref eq 'ARRAY') { for (my $i = 0; $i < scalar(@{$hashORarray_ref}); $i++) { analyse_user_inputs($hashORarray_ref->[$i]); } } return(\@the_inputs); }

        "I made a few changes but I am still only getting the 1st tier of keys"

        Then undo the changes, because the code I posted returns all levels of keys.

        The key part which you stripped out is...

        push @the_inputs, @{ analyse_user_inputs(...) };

        When you understand what that's doing, then you'll understand why you shouldn't have stripped it out.

        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'