views:

439

answers:

7

Hi,

I am new to Perl. I am writting a Perl script to download the same type of data from different sites. I have written the below code:

#! /usr/bin/perl

use strict;
use warnings;

my @sites = ('a', 'b', 'c');
my @servers = ('A', 'B');
my @data_type = ("X", "Y", "Z");

foreach my $site (@sites) {
        foreach my $server_type (@servers) {
                foreach my $data (@data_type) {
      do_functions......

I think using foreach everytime for each array can cause problems and values can mismatch. Can anyone please suggest me the better code using hashes, etc.

+1  A: 

foreach is preferable because it's readable. What exactly do you mean by "each array can cause problems" (what problems?) and "values can mismatch" (what values?)

Shaggy Frog
nested foreachs are a pain to read and maintain. Use an iterator solution such as Algorithm::Loops or Set::CrossProduct.
brian d foy
Looking at your comment, he'd have to download those from CPAN. Is it better to use built-in idioms like foreach, or force the user to download external packages? I prefer solutions that have as few external dependencies as possible.
Shaggy Frog
A: 

The only concern I might have when using nested loops is some ambiguity in what $_ is. Considering that you're not even using it, I don't think there's a better way to do what you want.

As a sidenote, I'd like to add that $_ is well defined in this case, but as a programmer I may not want to deal with the overhead of remembering what it refers to at each step.

Do you have any specific concerns with the code?

Nathan Fellman
Uh, he isn't using $_ anywhere.
jrockway
Which is why I think he should have no problems here.
Nathan Fellman
-1: "The only concern I might have here is use of egg noodles. You may not want to deal with the overhead of remembering what egg noodles have got to do with your code. However, since you avoid using it, you should have no problem with this code."
ire_and_curses
Right, except that egg noodles have nothing to do with loops in Perl, while `$_` does.
Nathan Fellman
+2  A: 

I don't see what your problem is, but you could use a generic Cartesian product if you are used to SQL or something:

sub cartesian {
    my @C = map { [ $_ ] } @{ shift @_ };
    foreach (@_) {
        my @A = @$_;
        @C = map { my $n = $_; map { [ $n, @$_ ] } @C } @A;
    }
    return @C;
}

my @sites = ('a', 'b', 'c');
my @servers = ('A', 'B');
my @data_type = ("X", "Y", "Z");

foreach (cartesian(\@sites, \@servers, \@data_type)) {
    ($data, $server_type, $site) = @$_;
    print "$site $server_type $data\n";
}
p00ya
I might suggest that your cartesian sub be prototyped, so that users don't have to use `\\` before all their lists. Also, ideally it should spit out the values in the same order they were put in.
Chris Lutz
Also, please `use strict` and `warnings`.
Chris Lutz
How would you prototype it?
Nathan Fellman
`sub cartesian (\@\@\@) {`
Chris Lutz
Clarification - that way, you would call it as `cartesian(@sites, @servers, @data_type)`, without the backslashes.
Chris Lutz
Even easier would be to use `Set::CrossProduct`, available on CPAN.
FM
Don't do it that way: you have to create all the tuples in memory. :(
brian d foy
A: 

You can use a classic for loop instead.

for(my $i = 0; $i <= $#sites; $i++){
    for(my $j = 0; $j <= $#servers; $j++){
        for(my $k = 0; $k <= $#data_type; $k++){
            do_functions ...

But that still leaves the problems and mismatches you were reffering to. I suggest you handle these issues in the do_functions part.

Ezekiel Rage
I'm pretty sure that code will skip the last element of each array. Better to use the `foreach` style, so you don't need to mess around with array indices.
Rob Kennedy
I don't understand why you would recommend a less-idiomatic, more verbose "solution" that's more like a mental speed bump to maintainers, and then say "but this doesn't solve your problem". If it doesn't solve their problem, or improve their code in some way, why are you posting an answer?
Chris Lutz
I edited this to at least be somewhat correct. Not that I would ever use <= in for loops starting from 0. $i < @array is much easier to read.
jrockway
He asked what he could use "instead". Of course this is not as nice looking as for each but is an alternative.
Ezekiel Rage
This isn't really an alternative. It's doing the same thing with the same problem, even if the typing looks a bit different.
brian d foy
In Perl `for` is simply an alias for `foreach`
Nathan Fellman
+3  A: 

You could simply use for.

(sorry, couldn't resist)

JB
I used to be a big advocate of `foreach` - I thought using `for` should be for C-style `for` loops, to make it easier to read for maintainers ("Is this a C-style or Perl-style loop?"). Then I realized that, in Perl, you almost never need the C-style `for` loop, and now I use it exclusively.
Chris Lutz
I think JB was referring to how "for" and "foreach" are synonyms. If the asker doesn't like foreach, he can choose to spell it "for" instead.
Rob Kennedy
Judging by my read of Chris Lutz's comment he understood that and was just relating that he used to reserve "for" for C-style loops and "foreach" for list iteration, then realized that he never needs C-style loops in Perl, so now he happily uses "for" exclusively. (And if that wasn't his story, it's mine anyhow.)
Dave Sherohman
Don't worry, Dave, it was my story.
Chris Lutz
In Perl 6 `foreach` is spelled `for` , and `for(;;)` is spelled `loop(;;)`
Brad Gilbert
+1  A: 

If I understand your question correctly then you asking how to use hashes with foreach to avoid mismatches that you would have in your array example?.

If so then here is one example:

use strict;
use warnings;

my %sites = (

    a => { 
        A => {
            data_type => [ 'X', 'Y' ],
        }
    },

    b => {
        B => {
            data_type => [ 'Y', 'Z' ],
        }
    },

    c => {

    },
);

for my $site ( keys %sites ) {
    for my $server ( keys %{ $sites{ $site } } ) {
        for my $data ( keys %{ $sites{ $site }{ $server } } ) {
            my @data_types = @{ $sites{ $site }{ $server }{ data_type } };
            say "On site $site is server $server with $data @data_types";
        }
    }
}


You can also use while & each which does produces easier code on the eye:

while ( my ( $site, $site_info ) = each %sites ) {
    while ( my ( $server, $server_info ) = each %{ $site_info } ) {
        my @data_types = @{ $server_info->{data_type} };
        say "On site $site we have server $server with data types @data_types"
            if @data_types;
    }
}

Also note I removed last loop in above example because its currently superfluous with my example hash data.

NB. If you plan to amend keys or break out of loop then please read up on each and how it affects the iteration.

/I3az/

PS. This example is not about the loop but about data being best represented as a Hash and not an Array! (though its not clear 100% from question that is so!).

draegtun
+2  A: 

Use my Set::CrossProduct module, or use Algorithm::Loops. You shouldn't have to create hard-coded, nested structures to deal with these issues. Both of those modules can do it for you for an arbitrary number of arrays.

Additionally, watch out for people who want to create all of the combinations in memory. There's no need to do that either.

brian d foy