2010-01-06 89 views
3

有一個Perl的腦傳情:爲什麼Perl的for()不會遍歷數組中的所有元素?

my @l = ('a', 'b', 'c'); 
for (@l) { 
    my $n = 1; 
    print shift @l while (@l and $n --> 0); 
    print "\n"; 
} 

什麼是它打印?應該是a,b和c,對嗎?但是,哦,等一下,實際上有一個地方的錯誤,它只打印一個和B。可能只是一些愚蠢的錯誤,應該很容易解決,對吧?

好了,所以做一個小的代碼更改測試的東西出來,改變@l到

my @l = ('a', 'b', 'c', 'd'); 

什麼是它打印?可能是a,b和c,因爲那個愚蠢的一個,對吧? ...等一下,實際上它仍然只打印a和b。好吧,所以錯誤在於它只打印前兩個字符。

變化@l再次

my @l = ('a', 'b', 'c', 'd', 'e'); 

嗯,現在它打印,b和c。但不是d或e。實際上,從現在開始我們添加的每兩個字母將使它打印序列中的下一個字母。所以如果我們添加f,它仍然會打印a,b和c,但是如果我們添加f和g,它會打印出a,b,c和d。

對於$ n的不同值,這也會發生類似的結果。

那麼這裏發生了什麼?

+4

Josh McAdams寫了有關有效Perl編程的第二版。總之,不要改變你正在迭代的列表。 – 2010-01-06 17:07:21

+5

啊,我最喜歡的操作符' - >'...... :) – Ether 2010-01-06 17:11:59

回答

1

我認爲這是某人的小工具代碼。它看起來不像你想寫任何東西的方式。但是,它可能說明最好的是(至少在某些版本)Perl是真正運行的更基本的for循環,其中:

for (@l) { 
    #... 
} 

被替換爲:

for (my $i = 0; $i < @l; $i++) { 
    local $_ = $l[$i]; 
    #... 
}  

因此,由於@l('c')當我們經歷了兩次,我們的旅行已經大於scalar(@l),所以我們出去了。我已經在很多情況下對它進行了測試,並且它們似乎是相同的。

下面是我寫給測試用例的代碼。從中我們可以看到,由於這種轉變,只要我們一半完成,循環就會退出。

use strict; 
use warnings; 
use English qw<$LIST_SEPARATOR>; 
use Test::More 'no_plan'; 

sub test_loops_without_shifts { 
    my @l = @_; 
    my @tests; 
    for (@l) { 
     push @tests, $_; 
    } 
    my @l2 = @_; 
    my $n = @tests; 
    my $i = 0; 
    for ($i = 0; $i < @l2; $i++) { 
     local $_ = $l2[$i]; 
     my $x = shift @tests; 
     my $g = $_; 
     is($g, $x, "expected: $x, got: $g"); 
    } 
    is($n, $i); 
    is_deeply(\@l, \@l2, do { local $LIST_SEPARATOR = .', '; "leftover: (@l) = (@l2)" }); 
    return $i; 
} 

sub test_loops { 
    my @l = @_; 
    my @tests; 
    for (@l) { 
     push @tests, shift @l; 
    } 
    my @l2 = @_; 
    my $n = @tests; 
    my $i = 0; 
    for ($i = 0; $i < @l2; $i++) { 
     local $_ = $l2[$i]; 
     my $x = shift @tests; 
     my $g = shift @l2; 
     is($g, $x, "expected: $x, got: $g"); 
    } 
    is($n, $i); 
    is_deeply(\@l, \@l2, do { local $LIST_SEPARATOR = ', 'c; "leftover: (@l) = (@l2)" }); 
    return $i; 
} 

is(test_loops('a'..'c'), 2); 
is(test_loops('a'..'d'), 2); 
is(test_loops('a'..'e'), 3); 
is(test_loops('a'..'f'), 3); 
is(test_loops('a'..'g'), 4); 
is(test_loops_without_shifts('a'..'c'), 3); 
is(test_loops_without_shifts('a'..'d'), 4); 
is(test_loops_without_shifts('a'..'e'), 5); 
is(test_loops_without_shifts('a'..'f'), 6); 
is(test_loops_without_shifts('a'..'g'), 7); 
+0

這是/完全/我正在尋找的答案。我決定不要進一步推行,只是接受「不這樣做」的答案,因爲似乎存在一些模糊的敵意,但我真正想知道的是爲什麼你不應該這樣做。謝謝! – John 2010-01-07 14:57:12

14

發生了什麼事情是您在同一時間使用forshift。所以你在修改它的同時循環了這個列表,而不是一個好主意。

+0

他也在用'while'。 – 2010-01-06 17:05:20

+5

@Paul - 是的,還有'print'和'my',但問題是'for' /'shift'組合。 – 2010-01-06 17:08:05

15

Dave Webb打我的問題,但這裏是從perldoc perlsyn說,不這樣做報價:

如果LIST的任何部分是一個數組,如果您添加或刪除中的元素foreach會得到非常困惑循環體,例如splice。所以不要這樣做。

需要注意的是,早在文中的foreach語法被描述爲foreach LIST,這是他們參考文檔中的LIST。還請注意,foreachfor是等效的。

+2

剛剛在我自己的答案中鏈接到文檔。 +1 – 2010-01-06 17:07:44

+0

這實際上並沒有讓人困惑(請參閱下面的測試)。它*雖然是令人混淆的語法! – Axeman 2010-01-07 06:32:30