Ermittelt automatisch den Schleifenindex in jeder Schleife in Perl


77

Wenn ich das folgende Array in Perl habe:

@x = qw(a b c);

und ich iteriere darüber mit foreach, dann $_verweise ich auf das aktuelle Element im Array:

foreach (@x) {
    print;
}

wird drucken:

abc

Gibt es eine ähnliche Möglichkeit, den Index des aktuellen Elements abzurufen, ohne einen Zähler manuell zu aktualisieren? So etwas wie:

foreach (@x) {
    print $index;
}

Wo $indexwird aktualisiert $_, um die Ausgabe zu erhalten:

012

Antworten:


110

Wie Codehead sagte , müssten Sie die Array-Indizes anstelle ihrer Elemente durchlaufen . Ich bevorzuge diese Variante gegenüber der C- forSchleife:

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

5
Dies ist nicht das, was Sie wollen, wenn die Array-Indexsequenz 2, 5, 8, 19 mit Lücken in der Sequenz ist. Ich glaube, die Frage bezog sich auf den Index des aktuellen Elements.
Andy N

Die einzige Möglichkeit, ein Array mit Lücken zu verallgemeinern, besteht darin, die Zeile next unless defined $x[$i];am Anfang der Schleife zu setzen. Dies scheint auch für alle riskanteren Lösungen zu gelten, die while / each und for / keys betreffen. Anscheinend sieht der Array-Iterator jeden sequentiellen Index, ob er existiert oder nicht.
Arnold Cross

Ich habe mir gerade einen besseren Weg ausgedacht, um diese Zeile zu schreiben:$x[$i] // next;
Arnold Cross

46

In Perl vor 5.10 kann man sagen

#!/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";
}

In Perl 5.10 verwenden Sie state , um eine Variable zu deklarieren, die niemals neu initialisiert wird (im Gegensatz zu denen, die mit my erstellt wurden ). Auf diese Weise können Sie die $indexVariable in einem kleineren Bereich halten, dies kann jedoch zu Fehlern führen (wenn Sie die Schleife ein zweites Mal betreten, hat sie immer noch den letzten Wert):

#!/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";
}

In Perl 5.12 kann man sagen

#!/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";
}

Aber seien Sie gewarnt: Sie haben Einschränkungen , was Sie tun dürfen, @awährend Sie darüber iterieren each.

Es wird dir jetzt nicht helfen, aber in Perl 6 kannst du es sagen

#!/usr/bin/perl6

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

Der ZOperator komprimiert die beiden Listen zusammen (dh er nimmt ein Element aus der ersten Liste, dann ein Element aus der zweiten, dann ein Element aus der ersten usw.). Die zweite Liste ist eine faule Liste, die (zumindest theoretisch) jede ganze Zahl von 0 bis unendlich enthält. Das -> $elem, $indexsagt, dass wir zwei Werte gleichzeitig aus dem Ergebnis der Zip nehmen. Der Rest sollte für Sie normal aussehen (es sei denn, Sie sind mit der sayFunktion ab 5.10 noch nicht vertraut ).


Ich mag den Abschnitt nicht state, ich denke, es wäre besser, wenn man ihn bedient { my $index; ... }.
Brad Gilbert

22

perldoc perlvar scheint keine solche Variable vorzuschlagen.


6
AndrewKS : Dies ist sicherlich nicht gegen den Geist von StackOverflow. Wenn Sie es versucht hättenperldoc perlvar, würden Sie wissen warum. Es sind nur die in aufgelisteten Variablenperldoc perlvarvorhanden. Eine solche Variable existiert, wie ich bereits sagte, nicht.
Alan Haggai Alavi

5
Dies ist gegen den Geist der Stackoverflow, weil der Geist eine Antwort zu geben, die eine Frage in seiner Gesamtheit, ohne externe Ressourcen beantworten können. Das Bereitstellen optionaler Ressourcen wie Links oder Terminalbefehle zum Ausführen zum Lernen ist ein Bonus, aber nicht der Geist . Die Tatsache, dass Sie in einem Kommentar weitere Informationen angeben mussten, ist ein Beweis dafür, dass diese Antwort allein nicht ausreicht.
SgtPooki

1
Dies entspricht im Wesentlichen einer Nur-Link-Antwort.
Jinawee

Vielleicht erweitern Sie Ihre Antwort?
Peter Mortensen

8

Nicht mit foreach.

Wenn Sie die Elementkardinalität im Array definitiv benötigen, verwenden Sie einen 'for'-Iterator:

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

5
foreach und for sind austauschbare Synonyme. In einigen einführenden Materialien wird versucht, die Schleife im C-Stil und foreach für die Listeniteratorschleife zu bezeichnen, aber die Verwendung der Terminologie ist nicht jedem bekannt.
Ysth

2
foreach und for sind / nicht / austauschbar, wie diese Instanz zeigt.
Matthew Flaschen

9
Zitat aus 'perldoc perlvar': Das Schlüsselwort "foreach" ist eigentlich ein Synonym für das Schlüsselwort "for", daher können Sie "foreach" für die Lesbarkeit oder "for" für die Kürze verwenden. endquote. Sie sind also austauschbar. Versuch es.
user55400

1
Die Schlüsselwörter sind austauschbar - aber die Schleifen selbst haben unterschiedliche Bedeutungen. Eine "for" -Schleife ist eine Schleife mit einer inkrementierenden (oder dekrementierenden) Variablen, selbst wenn sie mit dem Schlüsselwort foreach eingeführt wird. Eine "foreach" -Schleife verfügt über einen internen Iterator, auch wenn dieser mit dem Schlüsselwort for eingeführt wird. Das OP wollte Zugriff auf den Iterator, daher möchte er unabhängig vom verwendeten Schlüsselwort eine "for" -Schleife.
Andrew Barnett

4
Eine for-Schleife im C-Stil (die Sie immer wieder als "for-Schleife" bezeichnen) muss keine Variable inkrementieren / dekrementieren, z. B.: for (my $ iter = new_iter (); $ iter; $ iter = $ iter -> next ()) {...}
ysth

8

Dies kann mit einer whileSchleife erfolgen ( foreachunterstützt dies nicht):

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

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

Ausgabe:

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

Perl-Version: 5.14.4


Perldoc sagt, While-Each nicht zu verwenden --- Es ist einfach genug, den Iterator vor dem Starten einer Schleife explizit zurückzusetzen, aber es gibt keine Möglichkeit, den von einer Schleife verwendeten Iteratorstatus von dem Iteratorstatus zu isolieren, der von irgendetwas anderem verwendet wird, das während ausgeführt werden könnte der Schleifenkörper. Um diese Probleme zu vermeiden, verwenden Sie eine foreach-Schleife anstelle von while-each .--- perldoc.perl.org/functions/each.html
Able Mac

5

Nein, Sie müssen Ihren eigenen Zähler erstellen. Noch ein Beispiel:

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

wenn für die Indizierung verwendet

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

Und natürlich können Sie local $index;stattdessen my $index;und so und so verwenden.


1
0+ ist nicht erforderlich; postincrement gibt 0 zurück, wenn die inkrementierte Variable undef war.
Ysth

@ysth: Sie können überrascht sein, wenn jemand irgendwo in der Anwendung dieselbe globale $ index-Variable wie Sie verwendet. Aber wenn Sie ein kurzes Skript schreiben und annehmen ... nein, tun Sie es nicht.
Hynek-Pichi-Vychodil

Diese Antwort ist wohl besser lesbar als die forSchleifensyntax von for(my $index; $index <= $#x; $index++){}.
Able Mac

4

Ja. Ich habe so viele Bücher und andere Blogs überprüft ... Die Schlussfolgerung ist, dass es keine Systemvariable für den Schleifenzähler gibt. Wir müssen unseren eigenen Zähler machen. Korrigiere mich, wenn ich falsch liege.


Sie haben Recht, es gibt keinen zugänglichen Systemzähler. --- Und "mach unseren eigenen Zähler" kann auf viele Arten gemacht werden; Am gebräuchlichsten ist wahrscheinlich die for(aka foreach) mit der optionalen langen Syntax von for($i=0;$i<=$#array;$i++){}.
Able Mac

3

autobox::Corebietet unter anderem eine praktische forMethode:

use autobox::Core;

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

Schauen Sie sich alternativ ein Iteratormodul an, zum Beispiel: Array::Iterator

use Array::Iterator;

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

Siehe auch:


Ich würde die Links zu den Distributionen durch einen Link des Formulars ersetzen: search.cpan.org/perldoc/Array::Iterator
Brad Gilbert

@ Brad Gilbert: Geändert. Obwohl ich persönlich die Top-Level-Ansicht (Home) von search.cpan.org/dist/Array-Iterator
draegtun

Ich würde zustimmen, dass es manchmal nützlich ist, auf die Dist-Ansicht anstelle der Perldoc-Ansicht zu verweisen. Auch nur weil ich die Links ersetzen würde , heißt das nicht unbedingt, dass Sie sollten .
Brad Gilbert

@Brad Gilbert: Dies ist vielleicht ein gutes Beispiel für TIMTOWTDI in Perl? :) Wie auch immer, es ist kein Problem und wenn der Perldoc- Link auf SO immer häufiger wird, bin ich froh, ihn zu verwenden.
Draegtun

2

Oh ja, das kannst du! (irgendwie, aber du solltest nicht). each(@array)In einem skalaren Kontext erhalten Sie den aktuellen Index des Arrays.

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

Hier each(@a)ist in einem skalaren Kontext und gibt nur den Index zurück, nicht den Wert an diesem Index. Da wir uns in einer forSchleife befinden, haben wir bereits den Wert in $ _. Der gleiche Mechanismus wird häufig in einer while-each-Schleife verwendet. Gleiches Problem.

Das Problem tritt auf, wenn Sie es for(@a)erneut tun . Der Index ist nicht wie erwartet auf 0 zurück. es undeffolgt 0,1,2 ... eine Zählung. Der Perldoc von each()sagt, um dieses Problem zu vermeiden. Verwenden Sie eine forSchleife, um den Index zu verfolgen.

jeder

Grundsätzlich:

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

Ich bin ein Fan der alternativen Methode:

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

Netter Versuch, aber nein. Jeder greift nicht auf denselben Iterator zu, den die for-Schleife verwendet. Der Iterator von jedem ist ein Teil des Arrays oder Hashs. for erstellt eine eigene Liste von Aliasen für das Array und iteriert darüber. Sie können sehen, was ich meine, indem Sie beispielsweise each(@a)innerhalb der Schleife erneut verwenden print each(@a)."=< $_ >=".each(@a);.
Arnold Cross

1

Beachten Sie bitte:

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

Ich denke nicht, dass keys @xsich das so verhält, wie du denkst. Ich würde erwarten, dass es jedes gerade indizierte Element zurückgibt.
Nathan Fellman

for keys @xbefindet sich in einem Listenkontext, gibt den aktuellen Index zurück, und um den Wert zu erhalten, müssen Sie darauf verweisen $x[$_]--- Eine besser lesbare Möglichkeit, genau dasselbe zu schreiben, istfor my $idx (keys @x){print "Element at index $idx is $x[$idx]\n";}
Able Mac

1

Nun, es gibt diesen Weg:

use List::Rubyish;

$list = List::Rubyish->new( [ qw<a b c> ] );
$list->each_index( sub { say "\$_=$_" } );

Siehe List :: Rubyish .


0

In den meisten Fällen sollten Sie den Index nicht kennen müssen. Du kannst das:

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

In diesem Fall wäre die Ausgabe 2, 3, 4, da foreach einen Alias ​​für das eigentliche Element festlegt, nicht nur für eine Kopie.


Was würde passieren, wenn Sie die erste Zeile durch Folgendes ersetzen: my @arr = ('foo', 'bar', 'foo');
Anon

Es würde 1, 1, 1 drucken, da String-Skalare bei Verwendung im Zahlenkontext auf Null ausgewertet werden.
Anonymer Feigling

7
Ich weiß, dass ich den Index in den meisten Fällen nicht brauche. Die Frage ist, wie ich es am besten bekomme, wenn ich es brauche.
Nathan Fellman

0

Ich habe versucht wie ....

@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.

Zusamenfassend...

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

Sie schlagen also grundsätzlich vor, dass ich den Index manuell verfolge.
Nathan Fellman
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.