2010-01-06 3 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, так? Но о, подождите, там где-то есть ошибка, она печатает только буквы a и b. Наверное, просто какая-то глупость, должна быть легко решить, не так ли?

ИТАК сделать небольшое изменение кода, чтобы проверить вещи и изменить @l к

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

Что это печатать? Вероятно, a, b и c из-за этого глупо от одного, не так ли? ... Подождите секунду, на самом деле он все еще печатает только a и b. Хорошо, так что ошибка в том, что она печатает только первые два символа.

Изменить @l снова

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

Эмм, теперь печатает, б и в. Но не d или e. Фактически, каждые 2 буквы, которые мы добавим с этого момента, сделают печать следующей буквы в последовательности. Поэтому, если мы добавим f, он все равно будет печатать a, b и c, но если мы добавим f и g, он напечатает a, b, c и d.

Это также происходит с аналогичными результатами для разных значений $ n.

Так что же здесь происходит?

+4

Josh McAdams написал об этом для эффективного Perl Programming, 2nd Edition. Короче говоря, не изменяйте список, который вы повторяете. –

+5

А, мой любимый оператор, '->' ... :) – Ether

ответ

1

Я думаю, что это чей-то код гаджета. Это не похоже на то, как вы хотели бы написать что-нибудь. Но то, что он может показать лучшее, что (по крайней мере, в некоторых версиях) Perl действительно работает более основной цикл, где:

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

14

Что происходит, так это то, что вы используете for и shift одновременно. Таким образом, вы перебираете список, изменяя его, а не хорошую идею.

+0

Он также использует 'while'. –

+5

@Paul - Да, и 'print' и' my', но проблема заключается в комманде 'for' /' shift'. –

15

Dave Webb бить меня к этой проблеме, но вот цитата из perldoc perlsyn говоря не делать этого:

Если какая-либо часть СПИСКА является массивом, foreach получите очень смущен, если добавить или удалить элементы в тело петли, например, с splice. Так что не делай этого.

Заметим, что ранее в тексте, синтаксис foreach был описан как foreach LIST, который является LIST они ссылаются в документации. Отметим также, что foreach и for эквивалентны.

+2

Был как раз для ссылки на документацию в моем собственном ответе. +1 –

+0

На самом деле это не путается (см. Мои тесты ниже). Это * * запутанный синтаксис, хотя! – Axeman

Смежные вопросы