Как сделать перебор массива в php

Добавил пользователь Cypher
Обновлено: 04.10.2024

Цикл — это конструкция языка, которая позволяет выполнить блок кода больше одного раза.

Мы привыкли, что наши сценарии выполняются линейно: сверху вниз, строчка за строчкой, инструкция за инструкцией. Но что делать, если надо повторить какую-нибудь инструкцию несколько раз?
Например, как вывести на экран натуральные числа от 1 до 9? Есть очевидный способ:

Но, во первых, такой способ заставляет писать много кода. Во-вторых, что если требуется вывести последовательность из миллиона чисел? И, наконец, бывают ситуации, когда заранее неизвестно сколько раз нужно выполнить определённую инструкцию.

Использование циклов значительно упрощает и укорачивает код. Также циклы незаменимы в ситуациях, когда заранее неизвестно сколько раз должен выполниться блок кода. Такое число может зависеть от множества условий и вычисляться в момент выполнения сценария.

Так выглядит цикл в PHP:

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

Важно понимать последовательность, в которой исполняется код при использовании циклов.

  1. Обычное исполнение кода, строчка за строчкой, пока не встретился цикл.
  2. Встретился цикл: выполняем условие цикла.
  3. Если условие вернуло ложь: выходим из цикла, выполняем строчку после него и продолжаем линейное исполнение.
  4. Если условие вернуло истину: выполняем всё тело цикла.
  5. Повторяем пункт 2.

Каждая последовательность из шагов 2-4, то есть очередное выполнение блока кода в теле цикла — называется итерацией.

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

Теперь вернёмся к задаче по выводу на экран всех натуральных чисел:

Данный цикл в своём теле содержит две инструкции. Первая выводит на экран цифру из переменной. Вторая инструкция увеличивает значение переменной на единицу. Теперь вопрос: сколько раз будет исполнен такой цикл?

Циклы выполняются, пока их условие остаётся истинным, а в нашем условии значение переменной должно быть меньше десяти. Так как начальное значение переменной — единица, то не сложно посчитать, что цикл выполнится ровно девять раз. На десятый раз значение переменной $last_num станет равно десяти и условие $last_num перестанет быть истинным.

Циклы и массивы

Чаще всего циклы используются для работы с массивами. А конкретнее — для перечисления всех элементов массива и выполнения какого-нибудь действия с каждым из этих элементов.
Умение использовать циклы и массивы совместно, сразу позволит тебе выполнять множество интересных и разнообразных задач!

Чуть раньше мы уже научились работать с массивами. Например, мы можем показать все значения массива, обратившись к каждому из элементов по его индексу. Но это чрезвычайно утомительно: обращаться к каждому из элементов массива по очереди, когда мы просто хотим показать всего его значения. Циклы избавят от этой рутины!

С помощью циклов можно показать содержимое любого массива и это потребует всего несколько строк кода!

Перепишем пример с выводом списка любимых сериалов, но теперь задействовав цикл:

В этом примере цикл выводит элемент по индексу. Индекс теперь находится в переменной $cur_index и начальное значение у него ноль. Значение переменной увеличивается на единицу с каждой итерацией цикла, пока не достигнет трёх. В этот момент условие $cur_index перестанет быть истинным и цикл остановится, перебрав весь массив.

Потренируйтесь использовать циклы с массивами, пройдя этот тренажёр.

foreach — специальный цикл для массивов

Настало время узнать, что циклы в PHP могут быть разных типов. Выше мы познакомились с циклом типа while . Главная особенность этого типа в том, что необходимо указывать выражение в его условии. Но while - это не единственный вид циклов в PHP. Есть ещё как минимум два других.

Массивы и циклы так часто используются вместе, что разработчики языка даже добавили вид цикла специально для перебора массивов. Он называется foreach . Но что не так с обычным while и зачем понадобилось придумывать этот foreach ?

Дело в том, что цикл while слишком универсален. А платой за эту универсальность всегда будет более сложный и обьемный код. Приходится придумывать условие, следить, чтобы оно не было бесконечным. А в теле цикла обязательно надо не забыть увеличивать переменную-счётчик. И всё это нужно для простого перебора элементов массива. Неужели нельзя сделать проще?

К счастью, foreach решает все эти проблемы. Вот его возможности:

  • не требуется писать условие;
  • позволяет получать ключи массива;
  • сам присваивает очередной элемент массива переменной.

Цикл foreach становится совершенно незаменим, когда дело доходит до итерации по ассоциативным массивам. Возьмём такой пример: у нас есть данные пользователя, которые хранятся в ассоциативном массиве. На сайте понадобилось сделать страницу с информацией об этом юзере. Задача состоит в том, чтобы показать на странице все данные, которые известны об этом человеке. Выглядеть это должно так:

Оригинальный массив, который надо показать в таком виде:

Код сценария, который обойдёт массив и покажет всё его содержимое займёт всего 4 строчки:

На каждой итерации цикла, внутри его тела будут определяться переменные $key и $value . В первую будет попадать очередной ключ массива, а во вторую — очередное значение по этому ключу.

PHP имеет четыре вида циклов и операторы управления ими, рассмотрим поподробнее.

Foreach – перебор массива

Предназначен для перебора элементов массива.

Foreach – перебор массива

Результат:

Альтернативный синтаксис foreach

For – цикл с счетчиком

Цикл работает пока выполняется заданное условие. Обычно применяется в качестве счетчика.

For – цикл с счетчиком

Результат:

Альтернативный синтаксис

Результат:

Альтернативный синтаксис for

While – цикл с предусловием

Т.е. если перед началом итерации условие выполняется, то цикл продолжает свою работу.

While – цикл с предусловием

Результат:

Альтернативный синтаксис while

Do-while – цикл с постусловием

В отличии от while этот цикл проверяет выполнения условия после каждой итерации. Do-while не имеет альтернативного синтаксиса.

Do-while – цикл с постусловием

Результат:

Управление циклами

Break

Вызов break или break 1 в цикле останавливает его.

Для вложенных циклов используется break 2 и т.д.

Continue

Используется для пропуска итерации.

Результат:

Для вложенных циклов по аналогии с break 2 , break 3 – continue 2 , continue 3 .

Комментарии

Другие публикации

Работа с массивами PHP – создание, наполнение, удаление

Пример парсинга html-страницы на phpQuery

phpQuery – это удобный HTML парсер взявший за основу селекторы, фильтры и методы jQuery, которые позволяют.

Whois, как получить данные IP-адреса и домена в PHP

Генерация паролей в PHP

Вывести массив в виде PHP кода

Работа с FTP в PHP

Протокол FTP – предназначен для передачи файлов на удаленный хост. В PHP функции для работы с FTP как правило всегда доступны и не требуется установка дополнительного расширения.

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

Для последовательного перебора всех элементов массива в PHP существует цикл foreach. Он подходит для работы и с массивами, и с объектами. При этом следует учесть, что применение его с переменными прочих типов либо с переменными, которые неинициализированы, приведёт к появлению ошибки. Но давайте не будем забегать вперёд и рассмотрим всё по порядку.

Итак, для цикла foreach существуют 2 два вида синтаксиса. Первый из них реализуется так:

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

Сам по себе оператор цикла foreach является удобным в использовании, т. к. он, по сути, самостоятельно проходит по массиву и читает все его элементы до того момента, пока не достигнет последнего. Оператор удобен тем, что позволяет нам не держать в голове факт, что индексация массива начинается с нуля. К тому же, за пределы массивов он никогда не выходит, в результате чего конструкция цикла помогает избежать наиболее распространенных ошибок.

Рассмотрим работу оператора на примере перебора массива:

У оператора foreach есть и другой вид синтаксиса, давайте на него посмотрим:

Что особенного в применении этой формы? Дело в том, что на каждой итерации происходит дополнительное присваивание значения текущего ключа переменной $key (вы можете указать иное имя переменной). Вот, как это работает:

Если хотите менять элементы вашего массива внутри цикла напрямую, надо применять ссылку. В таком случае значение присвоится по ссылке.

На что тут важно обратить внимание? Дело в том, что ссылка на последний элемент вашего массива остаётся даже после завершения работы оператором foreach. В результате мы рекомендуем удалять её посредством функции unset() , как это реализовано в вышеописанном примере. Теперь посмотрим, что произойдёт, если мы не будем применять unset() :

Также надо отметить очередной нюанс: ссылку можно применять только в том случае, когда перебираемый нами массив является переменной. К примеру, код ниже работать не будет:

В целом, как видите, ничего сложного в переборе массива на PHP нет. Если же вы хотите получить продвинутые навыки в освоении этого языка, ждём вас на наших курсах!


Дело было вечером, делать было нечего. Самое время устроить небольшой разбор того, чем изнутри отличаются некоторые способы перебора массивов в PHP.

Исходники от master ветки (это сейчас 7.4 с вкраплениями 8)
Генератор опкодов от php 7.3.0.
Замеры производились на 7.3.6.

Может быть, на самом деле, там десятки или сотни наносекунд и тысячи тактов, но это всё равно настолько малые величины, что необходимость экономить на них говорит о том, что что-то в вашем коде не так.

Этап компиляции

for, foreach, do и while являются ключевыми словами языка, тогда как функции array_* – это функции стандартной библиотеки. Следовательно, при прочих равных, по первым парсер отработает на пару наносекунд быстрее.

Парсер

До токена statement путь будет одинаков для всех


Циклы определены на уровне statement:

Отличие for_exprs от просто expr только в том, что для первого допустима запись нескольких expr через запятую.

foreach_variable – это конструкция, которая помимо просто variable, также отслеживает распаковку с помощью list или [].

for_statement, foreach_statement, while_statement отличаются от стандартного statement тем, что в них добавлена возможность разбора альтернативного синтаксиса:


Вызов функций закопан гораздо глубже:


callable_variable, хм… Забавно, не правда ли? :)

Перейдём к опкодам

Для примера давайте возьмём простой перебор индексированного массива с печатью каждого ключа и значения. Понятно, что использование for, while и do для такой задачи не оправдано, но у нас цель просто показать внутреннее устройство.

foreach

Что тут происходит:

FE_RESET_R: создаёт итератор $4 для массива !0. Либо, если массив пуст, сразу переходит к инструкции 7.
FE_FETCH_R: совершает шаг итерации, извлекает текущий ключ в ~5, а значение в !1. Либо, если достигнут конец массива, переходит к инструкции 7.
Инструкции 3-6 не особо интересны. Тут происходит вывод и возврат к FE_FETCH_R.
FE_FREE: уничтожает итератор.

for, while, .

На самом деле это частный случай while


На самом деле это частный случай if+goto


Опкоды для всех трёх случаев будут практически идентичны. Разве что в случае с if, JMPNZ поменяется на пару JMPZ+JMP из-за входа в тело if'а.
Для цикла do опкоды будут незначительно отличаться из-за его постпроверочной природы.

А можно ещё и так поитерировать

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

Функции reset, next и key довольно легковесные, но накладные расходы на их вызов всё же есть. И, как мы увидим дальше, расходы эти велики.

Хотя такой подход очень сильно напоминает принцип работы foreach, между ними есть два принципиальных отличия.

1) Тогда как reset, next и key (и current тоже) работают напрямую с внутренним указателем массива, foreach использует собственный итератор и не меняет состояние внутреннего указателя.


2) При использовании foreach для итерации по значению, что бы вы не делали с массивом внутри цикла, проитерирован будет именно первоначальный набор данных


Что будет при итерации по ссылке, можно почитать в этом RFC. Там всё не очень просто.

array_walk с анонимной функцией

Так как используется пользовательская функция, то будет дополнительный набор опкодов.

Функция

Основной код

Поскольку array_walk, как и остальные функции стандартной библиотеки, является интринсиком, то в скомпилированных опкодах механизм итерации отсутствует.

INIT_FCALL: инициализируем вызов array_walk
SEND_REF: кладём ссылку на массив на стек вызова
DECLARE_LAMBDA_FUNCTION: объявляем анонимную функцию
SEND_VAL: кладём анонимную функцию на стек вызова
DO_ICALL: запускаем array_walk на выполнение

Далее там происходит магия с вызовом нашей лямбды для каждого элемента массива.

array_walk с использованием предопределённой функции

Не сильно отличается от вызова с анонимной, разве только чуть меньше накладных расходов на создание лямбды во время исполнения.


Выводы банальны. foreach заточен под итерирование массивов, тогда как остальные циклы – просто обёртка над if+goto.

Функции же стандартной библиотеки работают по принципу чёрного ящика.

Погружаемся чуть глубже

Для начала рассмотрим случай с for и его опкодом FETCH_DIM_R, использующимся для извлечения значения по ключу. Извлечение элемента идёт через поиск в хеш-таблице (ZEND_HASH_INDEX_FIND). В нашем случае извлечение идёт из упакованного массива (ключи – непрерывная числовая последовательность) – это довольно лёгкая и быстрая операция. Для неупакованных массивов она будет чуть подороже.

Теперь foreach (FE_FETCH_R). Тут все банально:

  1. Проверяем, не вышел ли указатель итератора за пределы массива
  2. Извлекаем текущие ключ и значение
  3. Инкрементируем указатель

Если совсем упрощённо, то (псевдокод):


На самом деле внутри всё сложнее, но суть одна – идёт довольно быстрый перебор хеш-таблицы без участия виртуальной машины PHP (не учитывая вызова пользовательской функции).

Ну и немного замеров

А то ведь какая же статья без замеров (по памяти получилось настолько одинаково, что убрал её измерение).

В качестве массива, по традиции, возьмём zend_vm_execute.h на 70.108 строк.

Каждое измерение запускал раз по 10, выбирая наиболее часто встречающееся по первым 4-м цифрам.

Подведём итоги

Анализируя результаты, не забываем учитывать, что они получены на 10 проходах по массиву из 70 тысяч элементов.

array_walk с лямбдой дышит ему в спину, но тут есть нюанс. Грядущий JIT может кардинально изменить ситуацию. А может и не изменить. Интересно будет посмотреть.
array_walk с использованием готовой функции – крепкий середнячок.

Так как при итерации по ссылке foreach работает несколько иначе (использует опкод FE_FETCH_RW вместо FE_FETCH_R), то сделал для него отдельный замер. Он действительно чуть-чуть быстрее получился.

Как оказалось, создание лямбды на лету – не самая дешёвая операция. Казалось бы, создаётся она всего 10 раз. Надо будет поизучать.

Все остальные способы показали примерно одинаковые результаты, с очень незначительным разрывом.

Спасибо за внимание!

Читайте также: