Подтвердить что ты не робот

Как читать два элемента за раз в Perl foreach?

То, что я ищу, это что-то вроде:

@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

В ответ на очень хорошее предложение ниже я должен указать, что этот script будет запущен на ком-то сервере сборки, и мне не разрешено использовать какие-либо модули из CPAN. Только стандартный Perl.

4b9b3361

Ответ 1

Я считаю, что правильный способ сделать это - использовать natatime, от 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 ([email protected])
{
    my $n = shift;
    my @list = @_;

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

Ответ 2

Я бы использовал сплайсинг.

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

Ответ 3

Я думаю, вы бы хотели сделать это по-другому. Попробуйте следующее:

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

Имейте в виду, что это уничтожит список, но он будет работать для этого небольшого цикла.

Ответ 4

Настройте некоторые тестовые данные и импортируйте 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";
    }

Зацикливание по массиву 2 за раз, используя List::MoreUtils   (natatime).

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

Ответ 5

Ближайшим эквивалентом, к сожалению, становится старая школа:

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

Мне нравится, что Джек М отвечает лучше, правда, хотя я бы написал его в сексуальном Perl:

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

Ответ 6

Если бы я мог использовать стандартный Perl без модулей, я бы, вероятно, спустился до цикла C-стиля, который подсчитывается на 2:

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

Однако, если вам нужно что-то необычное из одного из модулей, которые вы не можете использовать, вы можете просто добавить этот модуль в свой код. Если вы можете написать код, вы можете использовать модули. Возможно, вам просто нужно включить модуль со всем кодом, который вы доставляете, когда вы установите @INC соответствующим образом. Это основная идея inc:: Module:: Install и PAR.

Я трачу много времени на работу с системой сборки, которая создает собственный репозиторий CPAN, устанавливает свои зависимости из своего частного CPAN, а затем проверяет код. Наличие фермы сборки не исключает использование модулей; это делает локальная политика. Однако это может не иметь смысла во всех случаях, даже если это возможно.

Удачи,:)

Ответ 7

Возможно, вам захочется создать простую подпрограмму, чтобы она работала для вас.

Я предлагаю следующее:

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

Это неконструктивно.

Ответ 8

Как насчет функционального решения общего назначения.

use Carp; # so mapn can croak about errors

sub mapn (&[email protected]) {
    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 ([email protected]) {mapn {[@_]} shift, @_}
sub every ([email protected]); *every = \&by;

Функция mapn работает так же, как map, за исключением первого аргумента после того, как его блок представляет собой число элементов, которые нужно взять. Он помещает первый элемент в $_ и все элементы в @_ .

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

Следующие два идентичных субмастера, by и every создают полезные наречия для различных конструкций циклов. Они обрабатывают список с помощью mapn и возвращают список массивов refs желаемого размера

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

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

Я считаю, что это более чистое и интуитивно понятное решение, чем natatime или другие решения, такие как стиль c для цикла.

Ответ 9

Рискуя метку некромантии, я решил добавить еще один рюкзак Тима Тоди:

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

Неразрушающий, нет дубликатов списков, нет переменных состояния и достаточно кратким.

Ответ 10

Как поясняет 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

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

Он также работает для списков не только для массивов.

Ответ 12

Использование цикла 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";
}

edit. Я исправил свой пост, чтобы использовать скалярную функцию для получения размера массива, а также добавить некоторую проверку, если массив не содержит четного числа элементов.

Ответ 13

быстрое решение для небольших массивов:

for ( map {$_*2} [email protected]/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} [email protected]/2-1;
Результат

такой же

a:1, b:2, c:3

Ответ 14

Я придумал этот код для решения аналогичного требования:

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 $a and $b
    local(*$caller_a, *$caller_b);

    # This map is also the return value
    map {
        # assign to $a, $b as refs to caller 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.

Ответ 15

вот реализация 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";
}

Ответ 16

Я думаю, что более простым способом является использование старых бедных "каждый". Прямо вот так:

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

Обновлено:

Да, это неправильно. Сначала нужно преобразовать список в хэш, но он может быть слишком большим:

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

Ответ 17

Это можно сделать без разрушения, Эрик Стром просто фантастически 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-less):

Массивные срезы и C-стиль для цикла à la brian d foy и Том Кристиансен! Это можно прочитать как "использовать индекс ($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 по мере необходимости.