Странное поведение при изменении итеративного массива - PullRequest
0 голосов
/ 27 июля 2010

Это глупый пример, но показывает, в чем именно заключается моя проблема. В некоторых ситуациях массив успешно модифицируется, а в других - нет, почему? Значения даны для foreach по значению? И вывод также прикручен, некоторые строки, кажется, имеют '\ r \ n', другие нет.

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( $x % 3){ unset( $arr[$x]); } // this works
    else{ $arr[$x+1] += 1;} // this don't
    echo "[$x] => ${arr[$x+1]}";
}


print_r( $arr);

text.txt:

0
1
2
3
4
5
6

выход:

[0] => 2 [1] => 2
[2] => 3
[3] => 5 [4] => 5
[5] => 6 [6] => 1Массив
(
[0] => 0

[3] => 3

[6] => 6
[7] => 1
)

EDIT:

Пример на самом деле ничего не добился, бесполезно было просто показать, что произошло что-то неожиданное, поэтому вот что-то ближе к тому, что мне нужно сделать:

<?php

$arr = file('text.txt');
echo '<pre>';

foreach( $arr as $x => $line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}


print_r( $arr);

text.txt:

test0
test1word
test2

ожидаемые значения в массиве:

[0] => test0
[1] => test1
[2] => word test2

Ответы [ 6 ]

3 голосов
/ 27 июля 2010

Вы не можете изменять значения внутри итератора foreach, если не передаете по ссылке.Как указано на странице руководства PHP :

Примечание. Если на массив не ссылаются, foreach работает с копией указанного массива, а не с самим массивом.У foreach есть некоторые побочные эффекты на указатель массива.Не полагайтесь на указатель массива во время или после foreach без сброса его.

Таким образом, вам необходимо обновить строку foreach до:

foreach( $arr as $x => &$line){
...
3 голосов
/ 27 июля 2010

Модификация массива во время его итерации обычно небезопасна и может привести к неожиданному поведению.

0 голосов
/ 27 июля 2010

Ваш второй пример все еще ожидаемое поведение ... Вы работаете над копией массива и его значений, а не фактическими значениями массива , если вы не используете "по ссылке"

foreach( $arr as $x => &$line){
    if( preg_match("/word$/", $line)){
        $line = preg_replace( "/word$/", '', $line);
        $arr[$x+1] = 'word ' . $arr[$x+1];
    }
}
unset($line);

Обратите внимание на использование строки & $ вместо $ line, и всегда безопаснее сбрасывать после завершения цикла

EDIT

Цитата из руководства по PHP :

Примечание. Если на массив не ссылаются, foreach работает с копией указанный массив, а не массив сам. foreach имеет некоторые побочные эффекты на указатель массива. Не полагайтесь на указатель массива во время или после foreach без сброса настроек.

EDIT

Я не рекомендую использовать ссылки в foreach (), это очень медленно, в моем случае это было 16x помедленнее. Решение, чтобы добавить это строка: $ line = $ arr [$ x]; в начало цикла, кажется, делает некоторые фокусы магии и все работает как я ожидал

Не совсем магический трюк. Он просто перезаписывает значение $ line, извлеченное через цикл foreach, $ line прямо из массива через ключ ($ x).

YMMV, но мне кажется, это не намного медленнее.

Следующий тестовый скрипт:

$arr = range(1,9999);


$callStartTime = microtime(true);

foreach($arr as &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference was '.sprintf('%.4f',$callTime)." seconds<br />\n";


foreach($arr as $x => &$line) {
    $line += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access by reference (retrieving key as well) was '.sprintf('%.4f',$callTime)." seconds<br />\n";


$callStartTime = microtime(true);

foreach($arr as $x => $line) {
    $arr[$x] += 1;
}
unset($line);

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time and then access array element directly was '.sprintf('%.4f',$callTime)." seconds<br />\n";

$callStartTime = microtime(true);

foreach(array_keys($arr) as $x) {
    $arr[$x] += 1;
}

$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo '<br />Call time to access array_keys was '.sprintf('%.4f',$callTime)." seconds<br />\n";

возвращает следующие значения времени:

Call time to access by reference was 0.0018 seconds
Call time to access by reference (retrieving key as well) was 0.0039 seconds
Call time to access key and then access array element directly was 0.0077 seconds
Call time to access array_keys was 0.0071 seconds
0 голосов
/ 27 июля 2010

Чего вы пытаетесь достичь с этим?

if( $x % 3){ 

Когда $ x равен 0, $ x% 3 равно 0, следовательно, false; когда $ x равен 1, $ x% 3 равно 1, следовательно, верно; когда $ x равно 2, $ x% 3 равно 2, следовательно, true ... поэтому, когда $ x равно 0, оно будет увеличивать $ x + 1 (1), а затем быстро сбрасывать записи 1 и 2; приращение $ x + 1, когда $ x равно 3 (запись 4); затем сбросьте записи 4 и 5 и т. д.

Вы имеете в виду

if(( $x % 3) == 0) { 
0 голосов
/ 27 июля 2010

Хотя это ожидаемое поведение, оно сбрасывает все, что не кратно 3, а затем пытается увеличить неустановленную переменную на 1.

0 голосов
/ 27 июля 2010

Ну, ваш пример отменяет все элементы, которые не делятся на 3. Теперь в этом $arr[$x+1] += 1; Вы увеличиваете значение на единицу, и на следующей итерации оно будет отменено, так как оно не делится на 3.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...