如何在Perl foreach循环中一次读取两个项目?

时间:2009-02-20 14:41:29

标签: perl loops

我正在寻找的是:

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

返回:

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

为了回应下面的一个非常好的建议,我需要指定此脚本将在其他人的构建服务器上运行,并且我不允许使用CPAN中的任何模块。仅限标准Perl。

19 个答案:

答案 0 :(得分:38)

我认为正确的方法是使用来自List::MoreUtils

来自文档:

  

natatime BLOCK LIST

     

创建一个数组迭代器,用于在$n项的块中循环数组       一次。 (n一次,得到它?)。一个例子可能更好       解释比我能说的话。

示例:

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

打印

a b c
d e f
g

List::MoreUtils::natatime的实施:

sub natatime ($@)
{
    my $n = shift;
    my @list = @_;

    return sub
    {
        return splice @list, 0, $n;
    }
}

答案 1 :(得分:20)

我会使用拼接。

my @list = qw(1 2 3 4 5 6);
while(my ($i,$j) = splice(@list,0,2)) {
  print "i: $i, j: $j\n";
}

答案 2 :(得分:15)

我认为你想要以不同的方式做到这一点。 试试这个:

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

请记住,这会破坏列表,但它适用于那个小循环。

答案 3 :(得分:15)

设置一些测试数据,然后导入say

use Modern::Perl;
use List::AllUtils qw'zip';

my @array = zip @{['a'..'z']}, @{[1..26]} ;

使用增量变量进行简单循环。

    {
      my $i = 0;
      while(
        (my($a,$b) = @array[$i++,$i++]),
        $i <= @array # boolean test
      ){
        say "$a => $b";
      }
    }

使用List::Pairwise (pair)

循环对
    use List::Pairwise qw'pair';

    for my $pair (pair @array){
      my($a,$b) = @$pair;

      say "$a => $b";
    }

使用List::MoreUtils (natatime)一次循环数组2。

    use List::AllUtils qw'natatime';

    my $iter = natatime 2, @array;
    while( my($a,$b) = $iter->() ){
      say "$a => $b";
    }

将其强制转换为哈希值,然后遍历键。如果你不关心订单,这很有用。

    {
      my %map = @array;
      for my $key (keys %map){
        my $value = $map{$key};
        say "$key => $value";
      }
    }

答案 4 :(得分:10)

不幸的是,最接近的等同物是老去的:

for(my $ix = 0; $ix <= $#list; $ix += 2) {
    my $i = $list[$ix];
    my $j = $list[$ix + 1];
    print "i: $i, j:$j\n";
}

我更喜欢Jack M的答案,但是我会用更性感的Perl来写它:

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

答案 5 :(得分:7)

如果我只能使用没有模块的标准Perl,我可能会下降到一个C风格的循环,按2计算:

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

但是,如果您想要使用其中一个无法使用的模块,您可以将该模块添加到您的代码中。如果您可以编写代码,则可以使用模块。您可能只需要在模块中包含您在适当设置@INC时提供的所有代码。这是inc::Module::InstallPAR的基本概念。

我花了很多时间使用构建系统来创建自己的CPAN存储库,从其私有CPAN安装其依赖项,然后测试代码。拥有构建服务器场并不排除使用模块;这是当地的政策。但是,即使可能,这在所有情况下都没有意义。

答案 6 :(得分:4)

您可能想要创建一个简单的子程序,使其适合您。

我建议:

{
  my $cl_ind = 0;
  sub arrayeach(@) {
    my @obj = @_;
    if(($cl_ind+2) > @obj)
    {
      $cl_ind = 0;
      return;
    }
    $cl_ind+=2;
    return ($obj[$cl_ind-2],$obj[$cl_ind-1]);
  }
}

封闭使其干净利落。要使用arrayeach(它的工作方式类似于散列,而不需要对数组进行危险的强制操作:

my @temp = (1,2,3,4,5,6,1,2,3,4,5,6);
while( ($a,$b) = arrayeach(@temp)) {
  print "A $a AND $b\n";
}

这是非破坏性的。

答案 7 :(得分:4)

冒着死灵法术的标签,我决定再加上Tim Toady的背包:

for (0 .. $#list) {
    next if $_ % 2;
    my ($i, $j) = @list[$_, $_ + 1];
    say "i:$i, j:$j";
}

非破坏性,无重复列表,无状态变量且合理简洁。

答案 8 :(得分:3)

通用功能解决方案如何。

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;

mapn函数的工作方式与map类似,但块之后的第一个参数是要采用的元素数。它将第一个元素放在 $_ 中,将所有元素放在 @_ 中。

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

接下来的两个相同的子byevery为各种循环结构创建有用的副词。他们使用mapn处理列表,并返回所需大小的数组引用列表

print "@$_\n" for every 2 => 1..10;

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

我发现这是一个比natatime更清晰,更直观的解决方案,或者其他一种解决方案,比如c风格的循环。

答案 9 :(得分:3)

my $i;
for ( qw(a b c d) ) {
    if (!defined($i)) { $i = $_; next; }
    print STDOUT "i = $i, j = $_\n";
    undef($i);
}

输出:

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

它也适用于列表,不仅适用于数组。

答案 10 :(得分:2)

正如Mirod所解释的那样,它没有太多代码。这几乎是你所需要的。 (请注意,我没有对奇数列表等进行任何检查。)

#!/usr/bin/env perl
use strict;
use warnings;

my @list = qw/1 2 3 4 5 6/;
my $get_em = get_by(2, @list);

while ( my ($i, $j) = $get_em->() ) {
  print "i: $i, j: $j\n";
}

sub get_by {
  my $n = shift;
  my @list = @_;

  return sub {
    return splice @list, 0, $n;
  }
}

答案 11 :(得分:1)

使用for循环可以满足您的需求。

use strict;
use warnings;

my @list = qw(1 2 3 4 5 );
my $i = 0;

for ($i = 0; $i < scalar(@list); $i++)
{
    my $a = $list[$i];
    my $b = $list[++$i];
    if(defined($a)) {
        print "a:$a";
    }
    if(defined($b)) {
        print "b:$b";
    }   
    print "\n";
}

编辑:我更正了帖子,使用标量函数检索数组的大小,并在数组不包含偶数元素的情况下添加一些检查。

答案 12 :(得分:1)

小型阵列的快速解决方案:

for ( map {$_*2} 0..@list/2-1 ){
    my ($i, $j) = @list[$_,$_+1];
    print "i: $i, j:$j\n";
}

某种oneliner

数据:

@v = (a=>1, b=>2, c=>3);

print join ', ', map{sprintf '%s:%s', $v[$_], $v[$_+1]} grep {!($_%2)} 0..$#v

或者像这样的事情

print join ', ', map {sprintf '%s:%s', @v[$_,$_+1]} map {$_*2} 0..@v/2-1;

结果相同

a:1, b:2, c:3

答案 13 :(得分:0)

我想出了这个代码来解决类似的要求:

sub map_pairs(&\@) {
    my $op = shift;
    use vars '@array';
    local *array = shift;    # make alias of calling array

    return () unless @array;

    # Get package global $a, $b for the calling scope
    my ($caller_a, $caller_b) = do {
        my $pkg = caller();
        no strict 'refs';
        \*{$pkg.'::a'}, \*{$pkg.'::b'};
    };

    # Get index counter size.
    my $limit = $#array/2;

    # Localize caller's $a and $b
    local(*$caller_a, *$caller_b);

    # This map is also the return value
    map {
        # assign to $a, $b as refs to caller's array elements
        (*$caller_a, *$caller_b) = \($array[$_], $array[$_+1]);
        $op->();    # perform the transformation
    } 
    map { 2 * $_ } 0..$limit;  # get indexes to operate upon.
}

你这样使用它:

@foo = qw( a 1 b 2 c 3 );
my @bar = map_pairs { "$a is $b" } @foo;

得到:

@bar = ( 'a is 1', 'b is 2', 'c is 3' );

我一直想提交给List :: MoreUtils的维护者,但我没有提供XS版本。

答案 14 :(得分:0)

这是natatime的一个实现,它没有复制列表:

sub natatime {
  my $n = shift;
  my $list = \@_;

  sub {
    return splice @$list, 0, $n;
  }
}

my $it = natatime(3, qw(1 2 3 4 5 6));
while ( my @list = $it->() ) {
  print "@list\n";
}

答案 15 :(得分:0)

我认为更简单的方法是使用旧的穷人&#39;每个人。 像这样:

while (my ($key,$value) = each @list) {
        print "$key=$value\n";
}

更新:

是的,这是错的。首先应该将列表转换为哈希值,但它可能过于冒犯:

my %hash = (@list);
while (my ($key,$value) = each %hash) {
        print "$key=$value\n";
}

答案 16 :(得分:0)

这可以非破坏性地完成,Eric Strom's简直太棒了List::Gen

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

<强>输出

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

修改(添加无CPAN版本):

数组切片和C风格的循环àla brian d foyTom Christiansen!这可以理解为“使用索引($i)一次循环@list foreach $n个元素”:

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++ ;          
}

我们将结果作为匿名数组(->[0])的元素([ ])来访问。对于更通用的输出,插值数组切片可以单独使用,例如print "@list[$i..$i+$n]";根据需要更改$n的值。

答案 17 :(得分:0)

另一种方法,不完全清洁,但可用。每个都创建迭代器,你可以使用它两次。当参数是经典数组时,它返回索引和值,请阅读:https://perldoc.perl.org/functions/each.html

所以,你的代码可以是这样的:

my @array=qw(one two three four five); #five element as unpaired will be ignored
while (my ($i1,$one,$i2,$two)=(each(@array),each(@array)) {
  #we will use $ix for detect end of array
  next unless defined $i1 and defined $i2; #secure complete end of array
  print "fetched array elements: $one => $two\n";
};

上面的例子不会破坏源数据,反对移位或类似。 我希望这对任何人都有帮助。当然,使用普通迭代器的情况要好得多。

答案 18 :(得分:-1)

while ($#rec>0) {
  my($a,$b) = ( shift(@rec), shift(@rec) );
  print "($a,$b)\n";
}