Как ведет себя структура управления foreach, когда итерируемый массив / коллекция изменяется внутри цикла? - PullRequest
1 голос
/ 03 мая 2011

Я написал следующую функцию для выравнивания массивов:

function flatten() {
    $args  = func_get_args();
    $items = array();

    for ($i = 0; $i < count($args); $i++) {  // <-- (*)
        $arg =& $args[$i];

        if (is_array($arg))
            foreach ($arg as &$item)
                $args[] =& $item;
        else
            $items[] = $arg;
    }

    return $items;
}

Я хотел бы заменить строку for простой foreach ($args as &$arg). На каком основании? Однажды я написал класс, который реализует интерфейс Iterator, который, по сути, является основой работы структуры управления foreach. Если я правильно помню, то, что делает структура управления foreach, выглядит следующим образом:

  1. Используйте метод rewind(), чтобы установить внутреннюю переменную индекса в положение первого элемента.
  2. Используйте метод valid(), чтобы проверить, достигнут ли конец массива. Если это так, выход.
  3. Используйте методы key() и current() интерфейса `Iterator, чтобы получить ключ и значение текущего элемента массива.
  4. Используйте метод next(), чтобы установить внутреннюю переменную индекса в положение элемента сразу после текущего.
  5. Перейти к 2.

По крайней мере, так работает с пользовательскими классами. Я не уверен, как это будет работать со встроенным типом массива. Будет ли это работать так же? Могу ли я заменить строку for на foreach?

Ответы [ 3 ]

2 голосов
/ 04 мая 2011

Во-первых, я должен отметить, что, как правило, изменение содержимого коллекции при ее повторении считается «плохой вещью» - многие языки выдают исключение, если вы попробуете.Однако PHP не является одним из этих языков.

Здесь важны две вещи:

Во-первых, при использовании массивов PHP foreach создает копию массива и выполняет итерации по нему.В этом случае вы можете безопасно изменить исходный массив, но ваш foreach не увидит ни одного из этих изменений.В вашем случае это не сработает - новые значения, добавленные в конец $ args, не будут появляться в foreach.

Вы можете заставить PHP использовать исходный массив, перебирая ссылкув массив.В этом случае внутреннее поведение станет актуальным.PHP хранит внутренний указатель на «следующий» элемент массива.Если вы измените содержимое элемента массива, который «foreach» уже видел, вы не увидите изменений.Если вы измените содержимое массива где-то за пределами текущего элемента, вы увидите эти изменения.Это должно работать на вас, но я не знаю, буду ли я доверять этому.

1 голос
/ 04 мая 2011
$nums = array(1, 2, 3, 4);

$newNum = max($nums) + 1;
foreach ($nums as $num) {
    echo $num;
    if ($newNum > 10) {
        break;
    }
    $nums[] = $newNum++;
}

print_r($nums);

/* output
1234

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
)
*/

Используя ссылку foreach ($nums as &$num):

/* output
1234567

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 7
    [7] => 8
    [8] => 9
    [9] => 10
)
*/
1 голос
/ 04 мая 2011

for и foreach довольно взаимозаменяемы, но в этом случае вы не можете добавлять элементы в массив args и обрабатывать их сразу с помощью foreach, поэтому ваша функция не будет работать так же, если вы используете foreach.

Пара моментов, на которые следует обратить внимание в отношении кода: 1) помещение счетчика ($ args) во второй параметр цикла for означает, что он обрабатывается при каждой итерации цикла, что, если у вас действительно большой массив, может быть дорогим.

Я бы посчитал количество аргументов перед обработкой цикла, сохранил бы его в переменной и использовал бы его вместо количества ($ args) в аргументах for, затем добавлял 1 к счетчику каждый раз, когда вы добавляетеновый элемент в массиве args.Это было бы быстрее и потребляло бы меньше памяти.

2) Это можно было бы очистить, чтобы использовать рекурсию через функции, которые делали бы то же самое без нескольких циклов и использовали бы немного меньше кода.

...