Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

How would you handle arrays of arrays?

by gokuraku (Monk)
on Jan 09, 2009 at 13:51 UTC ( [id://735178]=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks,
I finished a script, after thinking of multiple ways to approach this, that does what I need and creates my directories and files I need for some tests that I run. Mostly I need to directories and files for detection, it's ok for some of the files to be 0 length they just need to be there; which is where the empty FD's come from. Some I copy for other tests, those files are local and are copied in because they have data we need. Rather than copy the directory trees (in total there are 16 directory trees, but I don't show them all here) I came up with the following. First I generated all the arrays that list the files and directories I need, m, d and f are described further on, here are two examples of the arrays I created.
my @configapp1 = ("m:configapp1", "d:conf", "f:dactest1", "f:conf,4K_logging.properties", "f:conf,logging.properties", my @configbbb = ("m:configbbb", "f:b1.0", "f:bb1.0.config");
I have an array of references to the arrays so I can make sure each one is done.
my @allDACDirs = (\@configapp1, \@configbbb);
Going through the array of references I build what I need:
# Determine the size of the array so we hit everything my $array_no = scalar(@allDACDirs); for( my $i = 0; $i < $array_no; $i++) { my $curr_dir; my $entry; # Read the array and populate foreach $entry (@{$allDACDirs[$i]}) { # Review value whether its a dir or file my($chk_val, $filename) = split(/:/,$entry); # m means we need to make a new root directory if ($chk_val eq "m") { mkdir("$location/$filename",0755) or warn "Could not mkdir: $!\n"; $curr_dir = "$location/$filename"; } # d means a directory under root (like bin or lib) elsif ($chk_val eq "d") { mkdir("$curr_dir/$filename",0755) or warn "Could not mkdir: $!\n"; } # f means a file has to be made elsif ($chk_val eq "f") { # If there is a , we need a sub_dir, then make # file in that directory if ($filename =~ /,/) { my($dir_name, $file) = split(/,/,$filename); if (!-d "$curr_dir/$dir_name") { mkdir("$curr_dir/$dir_name",0755) or warn "Could not mkdir: $!\n"; } # If file exists in current directory then copy if (-e "$local_dir/files/$file") { copy("$local_dir/files/$file", "$curr_dir/$dir_nam +e/$file") or die "Could not copy $filename: $!\n"; } else { # Else create the shell for the file open(FD,">$curr_dir/$dir_name/$file") || die "Could not make $file: $!\n"; } } else { # If file exists in current directory then copy if (-e "$local_dir/files/$filename") { copy("$local_dir/files/$filename", "$curr_dir/$fil +ename") or die "Could not copy $filename: $!\n"; } else { # Else create the shell for the file open(FD,">$curr_dir/$filename") || die "Could not make $filename: $!\n"; } } } } }
My question is more educational, with the numerous methods to handle things in Perl what are some of the ways this could have been done differently? I tried doing this with hashes or arrays of hashes without getting what I wanted, the array of arrays I was able to understand and follow. Or maybe I'm not good at following depth in multi-dimensional arrays. I'm sure some things could be more elegant as well, and it'd be a good learning opportunity for me to see what I could have done simpler, or better. Thanks!

Replies are listed 'Best First'.
Re: How would you handle arrays of arrays?
by toolic (Bishop) on Jan 09, 2009 at 14:22 UTC
    I'll admit that I don't understand your text description of what you are trying to accomplish. But, it seems from your code that you don't really need an array-of-arrays data structure; a simple array will suffice. Your two "for" loops just seem to flatten your AoA structure, so you might as well just use a simple array:
    my @allDACDirs = (@configapp1, @configbbb); for my $i (0 .. $#allDACDirs) { ...
    Or maybe I'm not good at following depth in multi-dimensional arrays.
    Data::Dumper is a handy tool for understanding Perl data structures.

    On a side note, you could save a little typing on your array constructors using qw:

    my @configbbb = qw( m:configbbb f:b1.0 f:bb1.0.config );

    Update: Here is a crack at a more complex data structure and how you would navigate it:

    use strict; use warnings; use Data::Dumper; my @allDACDirs = ( { dir => 'conf', root => 'configapp1', files => [ {subdir => 'conf', name => '4K_logging.properties'}, {subdir => 'conf', name => 'logging.properties' }, {name => 'dactest1'}, ] }, { root => 'configbbb', files => [ {name => 'b1.0'}, {name => 'bb1.0.config'}, ] } ); #print Dumper(\@allDACDirs); # for debug for my $href (@allDACDirs) { my %h = %$href; my $curr_dir = "$location/$h{root}"; # mkdir curr_dir... if (exists $h{dir}) { # mkdir curr_dir/$h{dir}... } for my $ref (@{ $h{files} }) { if (exists ${$ref}{subdir}) { # mkdir subdir... # create file... } else { # create file... } } }
Re: How would you handle arrays of arrays?
by gwadej (Chaplain) on Jan 09, 2009 at 14:36 UTC

    Any way that helps you understand is a good one. After all, this is Perl.<grin/>

    However, I do have a few suggestions. I find that small utility functions often reduce the difficulty of reading code. For example, you repeat a snippet of code in three places to create a directory. I would probably define:

    sub mkdir_if_needed { my ($dir) = @_; return if -d $dir; mkdir($dir,0755) or warn "Could not mkdir: $!\n"; return; }

    and then call it in multiple places. That reduces unnecessary noise in the code.

    The outer loop can also be done as a Perl foreach loop (instead of the C-style you are using). The beginning of the loop would look like:

    foreach my $dacdir ( @allDACDirs ) { my $curr_dir; my $entry; # Read the array and populate foreach $entry (@{$dacdir}) ...

    This removes the need for $array_no and $i.

    A minor step into a more complex data structure would be to replace the strings above with anonymous arrays, so you don't spend time reparsing. I would have made the following replacements in the first array:

    • "m:configapp1" would be [ 'm', 'configappl' ]
    • "d:conf" would be [ 'd', 'conf' ]
    • "f:dactest1" would be [ 'f', 'dactest1' ]
    • "f:conf,4K_logging.properties" would be [ 'f', 'conf', '4K_logging.properties' ]

    This is mostly because I tend to think in these kinds of structures. I might then suggest different characters for the two styles of f entries. One would not have the extra file entry and the other would.

    This might tempt me toward a more table-driven approach for processing the entries... But, I'll stop here before the abstraction level gets too deep (and we have to break out the hip-waders<grin/>).

    G. Wade

      I forgot to remind that Data::Dumper is your friend when dealing with more complex data structures.

      G. Wade
Re: How would you handle arrays of arrays?
by hbm (Hermit) on Jan 09, 2009 at 15:00 UTC
    Given your data sample, how about this?
    #!/opt/local/bin/perl use strict; use warnings; use File::Path; use File::Copy; my $source = '/tmp'; my $destination = '.'; my @cfgA = qw( configapp1/conf/dactest1 configapp1/conf/4k_logging.properties configapp1/conf/logging.properties ); my @cfgB = qw( configbbb/b1.0 configbbb/b1.0.config ); foreach (@cfgA, @cfgB) { my ($path, $file) = m|(.*)/(.*)|; &File::Path::mkpath($path,0,0755) unless -d $path; if (-f "$source/$file") { copy("$source/$file", "$destination/$path/"); } elsif (open(IN,">$destination/$path/$file")) { close(IN); } else { warn("Couldn't copy or create $destination/$path/$file!"); } }
    Update:Added $destination to the copy, open, and warn statements.
      Very nice, but all in all I have 16 arrays now, might make the foreach loop long and hard to read.

      I do like the data entries in the arrays though, I hadn't really used File module like that, but having each one as a single directory path would make it easier to read.
        As for the foreach getting too long, the following layout might be useful:
        foreach ( @cfgA, @cfgB, #@cfgC, # not this time! @cfgD, @cfgE, #@cfgF, # not! @cfgG, # etc ) { }
        But it might be easier to maintain the paths and files on your filesystem than in your script. I.e., create all the paths and files in your $local_dir; and just copy the desired paths; or perhaps tar up each configuration and untar the desired ones.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://735178]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (5)
As of 2024-04-19 06:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found