How do I read two items at a time in a Perl foreach loop?

前端 未结 19 1427
你的背包
你的背包 2020-12-14 15:12

What I\'m looking for is something like:

@list = qw(1 2 3 4 5 6);
foreach (@list) {
  #perl magic goes here 
  print \"i: $i, j:$j\\n\";
}

相关标签:
19条回答
  • 2020-12-14 15:33

    This can be done non-destructively, with Eric Strom's simply fantastic List::Gen:

    perl -MList::Gen=":utility" -E '@nums = "1" .. "6" ; 
          say "i:$_->[0] j:$_->[1]" for every 2 => @nums'
    

    Output:

    i:1 j:2 
    i:3 j:4 
    i:5 j:6 
    

    Edit (add a CPAN-less version):

    Array slices and C-style for loop à la brian d foy and Tom Christiansen! This can be read as "use an index ($i) to loop through a @list foreach $n elements at a time":

    use v5.16; # for strict, warnings, say
    
    my @list = "1" .. "6";
    my $n = 2 ;   # the number to loop by
    $n-- ;        # subtract 1 because of zero index
    
    foreach (my $i = 0 ; $i < @list ; $i += $n ) { 
      say "i:", [ @list[$i..$i+$n] ]->[0], " j:", [ @list[$i..$i+$n] ]->[1];
      $i++ ;          
    }
    

    We access the results as elements (->[0]) of an anonymous array ([ ]). For more generic output the interpolated array slice could be used on its own, e.g.: print "@list[$i..$i+$n]"; changing the value of $n as required.

    0 讨论(0)
  • 2020-12-14 15:34
    my $i;
    for ( qw(a b c d) ) {
        if (!defined($i)) { $i = $_; next; }
        print STDOUT "i = $i, j = $_\n";
        undef($i);
    }
    

    Outputs:

    i = a, j = b
    i = c, j = d
    

    It also works for lists, not only for arrays.

    0 讨论(0)
  • 2020-12-14 15:36

    I think you'd want to do this differently. Try this:

    while (scalar(@list) > 0) {
        $i = shift(@list);
        $j = shift(@list);
        print "i: $i, j:$j\n";
    } 
    

    Keep in mind that this will destroy the list, but it will work for that little loop.

    0 讨论(0)
  • 2020-12-14 15:37

    I believe the proper way to do this is to use natatime, from List::MoreUtils:

    from the docs:

    natatime BLOCK LIST

    Creates an array iterator, for looping over an array in chunks of $n items at a time. (n at a time, get it?). An example is probably a better explanation than I could give in words.

    Example:

     my @x = ('a' .. 'g');
     my $it = natatime 3, @x;
     while (my @vals = $it->())
     {
         print "@vals\n";
     }
    

    This prints

    a b c
    d e f
    g
    

    The implementation of List::MoreUtils::natatime:

    sub natatime ($@)
    {
        my $n = shift;
        my @list = @_;
    
        return sub
        {
            return splice @list, 0, $n;
        }
    }
    
    0 讨论(0)
  • 2020-12-14 15:42

    If I only could use standard Perl with no modules, I'd probably drop down to a C-style for loop that counts by 2:

    for( my $i = 0; $i &lt; @array; $i += 2 ) {
        my( $j, $k ) = @array[ $i, $i+1 ];
        ...
        }
    

    If you have an odd number of elements, you'll have to decide how to handle an extra element. Your problem may not care that you get an extra element that is undefined since you specifically need pairs.

    Simply reading past the end of an array does not change the array, so that part is fine.

    If you must have pairs, a simple tactic might be to add an appropriate value to the end of the array so you always end up with pairs. Likewise, you can remove the last element (or whichever element) to end up with an even number again. Those depend on your problem.

    Otherwise, you're doing slightly more work:

    for( my $i = 0; $i < @array; $i += 2 ) {
        push @pair, $array[$i];
        push @pair, $array[$i+1] if $i+1 <= $#array;
        ... 
        }
    

    However, if you wanted something fancy from one of the modules you can't use, you can just add that module to your code. If you can write code, you can use modules. You might just have to include the module with all of the code you deliver while you set @INC appropriately. This is the basic idea of inc::Module::Install and PAR.

    I spend a lot of my time working with a build system that creates its own CPAN repository, installs its dependencies from its private CPAN, and then tests code. Having a build farm doesn't preclude using modules; it's local policy that does. However, that might not make sense in all cases even though it's possible.

    0 讨论(0)
  • 2020-12-14 15:42

    How about a general purpose functional solution.

    use Carp; # so mapn can croak about errors
    
    sub mapn (&$@) {
        my ($sub, $n, @ret) = splice @_, 0, 2;
        croak '$_[1] must be >= 1' unless $n >= 1;
        while (@_) {
            local *_ = \$_[0];
            push @ret, $sub->(splice @_, 0, $n)
        }
        @ret
    }
    
    sub by ($@) {mapn {[@_]} shift, @_}
    sub every ($@); *every = \&by;
    

    The mapn function works just like map, except the first argument after its block is the number of elements to take. It places the first element in $_ and all of the elements in @_ .

    print mapn {"@_\n"} 2 => 1 .. 5;
    # prints
    1 2
    3 4
    5
    

    The next two identical subs, by and every create useful adverbs for the various looping constructs. They process the list with mapn, and return a list of array refs of the desired size

    print "@$_\n" for every 2 => 1..10;
    
    print map {"@$_\n"} grep {$_->[1] > 5} by 2 => 1..10;
    

    I find this to be a cleaner and more intuitive solution than natatime, or other one off solutions like a c style for loop.

    0 讨论(0)
提交回复
热议问题