Автоматично отримувати індекс циклу в циклі foreach у Perl


77

Якщо я маю такий масив у Perl:

@x = qw(a b c);

і я перебираю це за допомогою foreach, потім $_посилатимуться на поточний елемент у масиві:

foreach (@x) {
    print;
}

надрукує:

abc

Чи існує подібний спосіб отримати індекс поточного елемента без ручного оновлення лічильника? Щось на кшталт:

foreach (@x) {
    print $index;
}

де $indexоновлюється, як $_для отримання результату:

012

Відповіді:


110

Як сказав codehead , вам доведеться перебирати індекси масиву замість його елементів. Я віддаю перевагу цьому варіанту перед forциклом C-стилю :

for my $i (0 .. $#x) {
    print "$i: $x[$i]\n";
}

5
Це не те, що потрібно, якщо послідовність індексу масиву дорівнює 2, 5, 8, 19 із пробілами в послідовності. Я вважаю, що питання стосувалось індексу поточного елемента.
Andy N

Єдиний спосіб узагальнити для масиву з пробілами - поставити рядок next unless defined $x[$i];на початку циклу. Здається, це вірно і для всіх найбільш ризикованих рішень, що включають клавіші while / each та for /. Очевидно, ітератор масиву бачить кожен послідовний індекс, існує він чи ні.
Арнольд Крос

Я просто придумав кращий спосіб написати цей рядок:$x[$i] // next;
Арнольд Крос

46

У Perl до 5.10, можна сказати

#!/usr/bin/perl

use strict;
use warnings;

my @a = qw/a b c d e/;

my $index;
for my $elem (@a) {
    print "At index ", $index++, ", I saw $elem\n";
}

#or

for my $index (0 .. $#a) {
    print "At index $index I saw $a[$elem]\n";
}

У Perl 5.10 ви використовуєте state, щоб оголосити змінну, яка ніколи не переініціалізується (на відміну від створених за допомогою my ). Це дозволяє зберегти $indexзмінну в меншій області, але це може призвести до помилок (якщо ви введете цикл вдруге, він все одно матиме останнє значення):

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

my @a = qw/a b c d e/;

for my $elem (@a) {
    state $index;
    say "At index ", $index++, ", I saw $elem";
}

У Perl 5.12 можна сказати

#!/usr/bin/perl

use 5.012; # This enables strict
use warnings;

my @a = qw/a b c d e/;

while (my ($index, $elem) = each @a) {
    say "At index $index I saw $elem";
}

Але будьте застережені: у вас є обмеження щодо того, що вам дозволено робити @a, переглядаючи це each.

Зараз це вам не допоможе, але в Perl 6 ви зможете сказати

#!/usr/bin/perl6

my @a = <a b c d e>;
for @a Z 0 .. Inf -> $elem, $index {
    say "at index $index, I saw $elem"
}

ZОператор застібає два списки разом (тобто він приймає один елемент з першого списку, то один елемент з другого, то один елемент з першого, і так далі). Другий список - це лінивий список, який містить кожне ціле число від 0 до нескінченності (принаймні теоретично). Це -> $elem, $indexговорить про те, що ми беремо два значення одночасно з результату zip. Решта повинна виглядати нормально для вас (якщо ви ще не знайомі з sayфункцією з 5.10).


Мені не подобається розділ state, я думаю, що з ним краще б подати { my $index; ... }.
Brad Gilbert

22

perldoc perlvar схоже, не пропонує жодної такої змінної.


6
AndrewKS : Це, безумовно, не суперечить духу StackOverflow. Якби ти намагавсяperldoc perlvar, ти знав би чому. perldoc perlvarІснуютьлише змінні, перелічені в. Така змінна, як я вже зазначав, не існує.
Алан Аггей Алаві,

5
Це є порушенням духу StackOverflow, тому що дух повинен дати відповідь , який може відповісти на питання в його повному обсязі, без зовнішніх ресурсів. Надання необов’язкових ресурсів, таких як посилання або команди терміналу для запуску для навчання, є бонусом, але не духом . Той факт, що вам потрібно було надати додаткову інформацію в коментарі, є доказом того, що цієї відповіді недостатньо само по собі.
SgtPooki,

1
В основному це те саме, що відповідь лише на посилання.
jinawee

Можливо, розширити свою відповідь?
Пітер Мортенсен

8

Не з foreach.

Якщо вам точно потрібна потужність елемента в масиві, використовуйте ітератор 'for':

for ($i=0; $i<@x; ++$i) {
  print "Element at index $i is " , $x[$i] , "\n";
}

5
foreach і for є взаємозамінними синонімами. Деякі вступні матеріали намагаються використовувати для того, щоб означати цикл стилю C та foreach для циклу ітераторів списку, але це використання термінології буде не всім знайоме.
ysth

2
foreach та for є / не / взаємозамінними, як показує цей екземпляр.
Метью Флашен

9
Посилання з "perldoc perlvar": Ключове слово "foreach" насправді є синонімом ключового слова "for", тому ви можете використовувати "foreach" для читабельності або "for" для стислості. кінцевацитата. Таким чином , вони є взаємозамінними. Спробуй це.
user55400

1
Ключові слова взаємозамінні, але самі цикли мають різне значення. Цикл "for" - це цикл із збільшенням (або зменшенням) змінної, навіть якщо він введений із ключовим словом foreach. Цикл "foreach" має внутрішній ітератор, навіть якщо він введений із ключовим словом for. OP хотів отримати доступ до ітератора, тому він хоче цикл "for", незалежно від використовуваного ключового слова.
Ендрю Барнетт

4
стиль C для циклу (щоб усі ви продовжували викликати цикл "for") не повинен збільшувати / зменшувати змінну, наприклад: for (my $ iter = new_iter (); $ iter; $ iter = $ iter -> наступний ()) {...}
ysth

8

Це можна зробити за допомогою whileциклу ( foreachце не підтримує):

my @arr = (1111, 2222, 3333);

while (my ($index, $element) = each(@arr))
{
   # You may need to "use feature 'say';"
   say "Index: $index, Element: $element";
}

Вихід:

Index: 0, Element: 1111
Index: 1, Element: 2222
Index: 2, Element: 3333

Версія Perl: 5.14.4


Перлдок каже, що не використовувати While-Each --- Досить просто явно скинути ітератор перед початком циклу, але немає можливості ізолювати стан ітератора, який використовується циклом, від стану ітератора, що використовується будь-чим іншим, що може виконуватися тіло петлі. Щоб уникнути цих проблем, використовуйте цикл foreach, а не while-each .--- perldoc.perl.org/functions/each.html
Able Mac,

5

Ні, ви повинні зробити власний лічильник. Ще один приклад:

my $index;
foreach (@x) {
    print $index++;
}

при використанні для індексації

my $index;
foreach (@x) {
    print $x[$index]+$y[$index];
    $index++;
}

І звичайно ви можете використовувати local $index;замість цього my $index;і так і так.


1
0+ непотрібний; postincrement повертає 0, якщо збільшена змінна була undef.
ysth

@ysth: Ви можете бути здивовані, коли хтось десь у додатку використовує ту ж глобальну змінну $ index, що і ви. Але якщо ви пишете короткий сценарій і припускаєте ... ні, не робіть цього.
Hynek -Pichi- Vychodil

Ця відповідь, мабуть, читабельніша, ніж forсинтаксис циклу for(my $index; $index <= $#x; $index++){}.
Able Mac

4

Так. Я перевірив стільки книг та інших блогів ... Висновок полягає в тому, що для лічильника циклів не існує жодної системної змінної. Ми повинні зробити власний лічильник. Виправте мене, якщо я помиляюся.


Ви праві, немає доступного системного лічильника. --- А "зробити власний лічильник" можна різними способами; найпоширеніший, мабуть, for(aka foreach) з необов'язковим довгим синтаксисом for($i=0;$i<=$#array;$i++){}.
Able Mac

3

autobox::Coreсеред багатьох інших речей пропонує зручний forметод:

use autobox::Core;

['a'..'z']->for( sub{
    my ($index, $value) = @_;
    say "$index => $value";
});

Або подивіться на модуль ітератора, наприклад: Array::Iterator

use Array::Iterator;

my $iter = Array::Iterator->new( ['a'..'z'] );
while ($iter->hasNext) {
    $iter->getNext;
    say $iter->currentIndex . ' => ' . $iter->current;
}

Також див.


Я б замінив посилання на дистрибутиви посиланням у формі: search.cpan.org/perldoc/Array::Iterator
Brad Gilbert

@ Бред Гілберт: Змінено. Хоча особисто я віддаю перевагу вигляду верхнього рівня (домашнього), представленому search.cpan.org/dist/Array-Iterator
draegtun

Я б погодився, що іноді корисно вказувати на погляд Dist, а не на погляд perldoc. Те, що я замінював би посилання, не обов'язково означає, що ви повинні .
Brad Gilbert

@ Бред Гілберт: Це хороший приклад TIMTOWTDI в perl? :) У будь-якому разі це не проблема, і якщо посилання perldoc стає все більш поширеним на SO, то я із задоволенням перейду на його використання.
draegtun

2

О так, ти можеш! (начебто, але не слід). each(@array)у скалярному контексті дає вам поточний індекс масиву.

@a = (a..z);
for (@a) { 
  print each(@a) . "\t" . $_ . "\n"; 
}

Ось each(@a)у скалярному контексті і повертає лише індекс, а не значення цього індексу. Оскільки ми в forциклі, ми вже маємо значення в $ _. Той самий механізм часто використовується в циклі while-each. Та сама проблема.

Проблема виникає, якщо ви for(@a)повторите. Індекс не повертається до 0, як ви очікували; за ним undefйде 0,1,2 ... один відлік. Перлдок each()каже, щоб уникати цієї проблеми. Використовуйте forцикл для відстеження індексу.

кожен

В основному:

for(my $i=0; $i<=$#a; $i++) {
  print "The Element at $i is $a[$i]\n";
}

Я шанувальник альтернативного методу:

my $index=0;
for (@a) {
  print "The Element at $index is $a[$index]\n";
  $index++;
}

Приємна спроба, але ні. кожен не має доступу до того самого ітератора, який використовує цикл for. Ітератор кожного з них є частиною масиву або хешу. for створює власний список псевдонімів для масиву та перебирає його. Ви можете зрозуміти, що я маю на увазі, наприклад, використовуючи each(@a)знову в циклі print each(@a)."=< $_ >=".each(@a);.
Арнольд Крос

1

Будь ласка, враховуйте:

print "Element at index $_ is $x[$_]\n" for keys @x;

Я не думаю, що це keys @xповодиться так, як ти думаєш. Я очікував би, що він поверне кожен індексований елемент.
Натан Феллман

for keys @xзнаходиться в контексті списку, повертає поточний індекс, і щоб отримати значення, вам потрібно на нього посилатись $x[$_]--- Більш читабельний спосіб написати точно те самеfor my $idx (keys @x){print "Element at index $idx is $x[$idx]\n";}
Able Mac


0

Вам не потрібно знати індекс у більшості обставин. Ви можете зробити це:

my @arr = (1, 2, 3);
foreach (@arr) {
    $_++;
}
print join(", ", @arr);

У цьому випадку результат буде 2, 3, 4, оскільки foreach встановлює псевдонім фактичного елемента, а не просто копії.


Що станеться, якщо замінити перший рядок на: my @arr = ('foo', 'bar', 'foo');
Анон,

Він буде друкувати 1, 1, 1, оскільки рядкові скаляри обчислюються нулем при використанні в контексті числа.
анонімний боягуз

7
Я знаю, що індекс мені не потрібен у більшості обставин. Питання в найкращому способі отримати його, коли мені це потрібно.
Nathan Fellman

0

Я пробував, як ....

@array = qw /tomato banana papaya potato/;             # Example array
my $count;                                             # Local variable initial value will be 0.
print "\nBefore For loop value of counter is $count";  # Just printing value before entering the loop.

for (@array) { print "\n",$count++," $_" ; }           # String and variable seperated by comma to
                                                       # execute the value and print.
undef $count;                                          # Undefining so that later parts again it will
                                                       # be reset to 0.

print "\nAfter for loop value of counter is $count";   # Checking the counter value after for loop.

Коротко...

@array = qw /a b c d/;
my $count;
for (@array) { print "\n",$count++," $_"; }
undef $count;

отже, ви в основному пропонуєте мені відстежувати індекс вручну.
Nathan Fellman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.