是否可以在Perl foreach循环中分配两个变量?

时间:2015-04-12 06:51:14

标签: arrays perl loops variables foreach

是否可以在Perl foreach循环中为数组中的两个变量分配相同的数据?

我正在使用Perl 5,我想我在Perl 6中遇到了一些东西。

这样的事情:

my $var1;
my $var2;

foreach $var1,$var2 (@array){...}

4 个答案:

答案 0 :(得分:13)

它不是Perl 5核心语言,但List::Util有一个pairs函数应该足够接近(以及许多其他pair...函数可能是更方便,取决于你在循环中做的事情):

#!/usr/bin/env perl    

use strict;
use warnings;
use 5.010;

use List::Util 'pairs';

my @list = qw(a 1 b 2 c 3);

for my $pair (pairs @list) {
  my ($first, $second) = @$pair;
  say "$first => $second";
}

输出:

a => 1
b => 2
c => 3

答案 1 :(得分:9)

最简单的方法是使用while循环,每次在数组的前两个元素上调用splice

while (my($var1, $var2) = splice(@array, 0, 2)) {
    ...
}

但是,与foreach不同,这会不断对原始数组进行双重移位,因此当您完成时,数组为空。此外,分配的变量是副本,而不是foreach的别名。

如果您不喜欢,可以使用C风格的for循环:

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

这使得数组保持不变,但不允许您以foreach的方式更新。为此,您需要直接处理数组。

my @pairlist = (
    fee => 1,
    fie => 2,
    foe => 3,
    fum => 4,
);

for (my $i = 0; $i < @pairlist; $i += 2) {
    $pairlist[ $i + 0 ] x= 2;
    $pairlist[ $i + 1 ] *= 2;
}

print "Array is @pairlist\n";

打印出来:

Array is feefee 2 fiefie 4 foefoe 6 fumfum 8

可以 如果你努力尝试就把它们变成别名变量,但它可能不值得:

my @kvlist = ( 
    fee => 1,
    fie => 2,
    foe => 3,
    fum => 4,
);

for (my $i = 0; $i < @kvlist; $i += 2) { 
    our  ($key, $value);
    local(*key, $value) = \@kvlist[ $i, $i + 1 ];
    $key   x= 2;
    $value *= 2;
}

print "Array is @kvlist\n";

打印出预期更改的数组:

Array is feefee 2 fiefie 4 foefoe 6 fumfum 8

请注意List::Pairwise模块提供的对,但 非常最近已添加到核心List::Util模块中(所以你可能无法使用它),仍然没有给你别名:

use List::Util 1.29 qw(pairs);

my @pairlist = (
    fee => 1,
    fie => 2,
    foe => 3,
    fum => 4,
);

for my $pref (pairs(@pairlist)) {
    $pref->[0] x= 2;
    $pref->[1] *= 2;
}

print "Array is @pairlist\n";

仅打印出来:

Array is fee 1 fie 2 foe 3 fum 4

所以它根本没有改变阵列。哎呀。 :(

当然,如果这是一个真正的哈希值,你可以简单地将值加倍:

for my $value (values %hash) { $value *= 2 }

有效的原因是因为它们是实际哈希值的别名。

您无法更改密钥,因为它们是不可变的。但是,您可以轻松地创建一个新哈希,它是旧版本的更新副本:

my %old_hash = (
    fee => 1,
    fie => 2,
    foe => 3,
    fum => 4,
);

my %new_hash;    
@new_hash{ map { $_ x 2 } keys   %old_hash } = 
           map { $_ * 2 } values %old_hash;

print "Old hash is: ", join(" " => %old_hash), "\n";
print "New hash is: ", join(" " => %new_hash), "\n";

输出

Old hash is: foe 3 fee 1 fum 4 fie 2
New hash is: foefoe 6 fiefie 4 fumfum 8 feefee 2

答案 2 :(得分:2)

超过2个变量的通用算法:

while( @array ){
  my $var1 = shift @array;
  my $var2 = shift @array;
  my $var3 = shift @array;
  # other variables from @array

  # do things with $var1, $var2, $var3, ...
}

PS:使用数组的工作副本保存它以供以后使用:

if( my @working_copy = @array ){
  while( @working_copy ){
    my $var1 = shift @working_copy;
    my $var2 = shift @working_copy;
    my $var3 = shift @working_copy;
    # other variables from @working_copy

    # do things with $var1, $var2, $var3, ...
  }
}

PPS:另一种方法是使用索引。当然,这是数据结构错误的明确信号。它应该是一个数组(AoA)数组或一个哈希数组(AoH)。请参阅perldoc perldscperldoc perllol

my $i = 0;
while( $i < @array ){
  my $var1 = $array[ $i++ ];
  my $var2 = $array[ $i++ ];
  my $var3 = $array[ $i++ ];
  # other variables from @array

  # do things with $var1, $var2, $var3, ...
}

PPPS:我被要求澄清数据结构错误的原因。它是一组扁平的元组(也就是数据集的记录)。通过计算每个元组的数据来重新创建元组。但构建该集合的读者有什么错误,并不总能得到正确的数字?如果,对于缺失值,它只是跳过添加任何东西?然后所有剩余的元组都被移动一个,导致后面的元组被错误地分组,因此无效。这就是AoA更好的原因;只有丢失数据的元组才会无效。

但更好的结构将是AoH。每个数据都可以通过密钥访问。然后可以添加新的或可选的数据,而不会破坏下游的代码。

虽然我在这里,但我会添加一些代码示例:

# example code for AoA
for my $tuple ( @aoa ){
  my $var1 = $tuple->[0];
  my $var2 = $tuple->[1];
  my $var3 = $tuple->[2];
  # etc
}

# example code for AoH
for my $tuple ( @aoh ){
  my $var1 = $tuple->{keyname1};
  my $var2 = $tuple->{key_name_2};
  my $var3 = $tuple->{'key name with spaces'};
  my $var4 = $tuple->{$key_name_in_scalar_variable};
  # etc
}

答案 3 :(得分:0)

这是一种无模块的方法,可以通过任意值($by)“循环”,并使用数组切片输出生成的元素组:

#!perl -l
@array = "1".."6"; 
$by = 3; $by--; 

for (my $i = 0 ; $i < @array ; $i += $by ) { 
      print "@array[$i..$i+$by]"; 
      $i++ ;
}

作为测试的单行程序(剪切并粘贴到Unix shell):

perl -E '@array = "1".."6"; $by = 3; $by--; 
      for (my $i = 0 ; $i < @array ; $i += $by ) { 
      say "@array[$i..$i+$by]"; $i++ }'

<强>输出:

1 2 3
4 5 6

如果你制作$by = 2;,它会打印成对的数字。要获取结果切片的特定元素,请将其作为匿名数组访问:(例如 [@array[$i..$i+$by]]->[1])。

另见:

那里有一些很好的回应,包括对natatime的引用,这很容易使用。它也很容易实现 - 它本质上是响应中提到的splice解决方案的包装器。

以下不是最好的例子,但我一直在使用autobox::Core并制作了@array->natatime()“方法”;-),如下所示:

use autobox::Core ;
sub autobox::Core::ARRAY::natatime {
  my ($self, $by) = @_; 
  my @copy = @$self ;
  my @array ;

  push @array, [splice (@copy, 0, $by) ] while @copy ;

  if  ( not defined wantarray ) { 
      print "@{ $_ } \n" for @array ;
  } 

  return wantarray ? @array : \@array;      
}

@copy数组是spliced破坏性的,但是$self(这就是自动装箱方法@array箭头前面的->传递给功能)仍在那里。所以我可以这样做:

my @dozen = "1" .. "12" ;           # cakes to eat
@dozen->natatime(4)  ;              # eat 4 at time
my $arr_ref = @dozen->natatime(4) ; # make a reference
say "Group 3: @{ $arr_ref->[2] }" ; # prints a group of elements
say scalar @dozen , " cakes left" ; # eat cake; still have it

<强>输出

1 2 3 4 
5 6 7 8 
9 10 11 12 
Group 3: 9 10 11 12
12 cakes left

另一种也使用CPAN模块的方法(我给了this answer elsewhere,但值得重复)。这也可以通过Eric Strom's优秀的List::Gen模块非破坏性地完成:

perl -MList::Gen=":all" -E '@n = "1".."6"; say "@$_" for every 2 => @n'
1 2
3 4
5 6

您抓取的每组元素都以匿名数组的形式返回,因此各个值位于:$_->[0] $_->[1] ...

你提到了Perl6,它可以很好地处理多个循环值:

my @qarr = 1 .. 6;
my ($x, $y, $z) ;
for @qarr -> $x , $y , $z { say $x/$y  ; say "z = " ~ $z }

<强>输出:

0.5
z = 3
0.8
z = 6

有关Perl6方法的更多信息,请参阅2009 Perl6 Advent Calendar中的Looping for Fun and ProfitBlocks and Statements Synopsis以获取详细信息。或许Perl 5将有一天类似的“多个循环值循环”构造 ​​- àla perl5i's foreach :-)