web-dev-qa-db-ja.com

Perlのforeachループで一度に2つの項目を読み取るにはどうすればよいですか?

私が探しているのは次のようなものです。

@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のみ。

36
Sean Cavanagh

これを行う適切な方法は、 List :: MoreUtils :からnatatimeを使用することだと思います。

ドキュメントから:

natatimeブロックリスト

一度に$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;
    }
}
39
mirod

スプライスを使います。

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

いくつかのテストデータを設定し、インポート 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";
      }
    }
16
Brad Gilbert

これを別の方法で行いたいと思います。これを試して:

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

これによりリストが破棄されますが、その小さなループでは機能することに注意してください。

16
Jack M.

最も近い同等物は、残念ながら、古い学校に行くことです:

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

私はジャックMの答えの方が本当に好きですが、もっとセクシーなPerlで書きます。

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

モジュールなしで標準のPerlしか使用できない場合は、おそらく2でカウントするCスタイルのforループにドロップダウンします。

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

ただし、使用できないモジュールの1つから何か凝ったものが必要な場合は、そのモジュールをコードに追加するだけです。コードを書くことができれば、モジュールを使うことができます。 @INCを適切に設定するときに、提供するすべてのコードにモジュールを含める必要がある場合があります。これは inc :: Module :: Install および [〜#〜] par [〜#〜] の基本的な考え方です。

私は、独自のCPANリポジトリを作成し、その依存関係をプライベートCPANからインストールしてから、コードをテストするビルドシステムで多くの時間を費やしています。ビルドファームがあるからといって、モジュールの使用が妨げられるわけではありません。そうするのはローカルポリシーです。ただし、それが可能であっても、すべての場合に意味があるとは限りません。

7
brian d foy

ネクロマンシータグを危険にさらして、TimToadyのバックパックからもう1つ追加することにしました。

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

非破壊、重複リスト、状態変数がなく、適度に簡潔です。

4
Zano

簡単なサブルーチンを作成して、それを機能させることをお勧めします。

私はこれを提案します:

{
  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";
}

これは非破壊的です。

4
user54650
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

配列だけでなく、リストでも機能します。

3
Loïc Etienne

汎用の機能ソリューションはどうですか。

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

次の2つの同一のサブ、byeveryは、さまざまなループ構造に役立つ副詞を作成します。それらはmapnでリストを処理し、目的のサイズの配列参照のリストを返します

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

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

これは、natatimeや、cスタイルのforループのような他の1回限りのソリューションよりもクリーンで直感的なソリューションであることがわかりました。

3
Eric Strom

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;
  }
}
2
Telemachus

小さなアレイの迅速な解決策:

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

ある種のワンライナー

データ:

@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
1
Sergey Markin

別のアプローチ、完全にクリーンではありませんが、使用可能です。それぞれがイテレータを作成し、2回使用できます。パラメータがクラシック配列の場合、インデックスと値を返します。以下をお読みください: 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";
};

上記の例では、シフトなどに対してソースデータを破棄しません。これが誰にとっても役立つことを願っています。もちろん、プレーンイテレータを使用した場合の方がはるかに優れています。

1
Znik

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

編集:スカラー関数を使用して配列のサイズを取得し、配列に偶数の要素が含まれていない場合のチェックを追加するように投稿を修正しました。

1

もっと簡単な方法は、古い貧しい「それぞれ」を使うことだと思います。このようにまっすぐ:

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

更新しました:

はい、それは間違っています。最初にリストをハッシュに変換する必要がありますが、コストがかかりすぎる可能性があります。

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

リストのコピーを作成しない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";
}
0
runrig

私は同様の要件を解決するためにこのコードを思いついた:

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バージョンがありません。

0
daotoad

これは非破壊的に行うことができ、 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スタイルのforループàlabrian d foy および Tom Christiansen !これは、「インデックス($i)を使用して、一度に@listforeach$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の値を変更します。

0
G. Cito