foreach номер итерации php
foreach
Конструкция foreach предоставляет простой способ перебора массивов. Foreach работает только с массивами и объектами, и будет генерировать ошибку при попытке использования с переменными других типов или неинициализированными переменными. Существует два вида синтаксиса:
Первый цикл перебирает массив, задаваемый с помощью array_expression. На каждой итерации значение текущего элемента присваивается переменной $value и внутренний указатель массива увеличивается на единицу (таким образом, на следующей итерации цикла работа будет происходить со следующим элементом).
Второй цикл будет дополнительно соотносить ключ текущего элемента с переменной $key на каждой итерации.
Когда оператор foreach начинает исполнение, внутренний указатель массива автоматически устанавливается на первый его элемент Это означает, что нет необходимости вызывать функцию reset() перед использованием цикла foreach.
Так как оператор foreach опирается на внутренний указатель массива, его изменение внутри цикла может привести к непредсказуемому поведению.
Для того, чтобы напрямую изменять элементы массива внутри цикла, переменной $value должен предшествовать знак &. В этом случае значение будет присвоено по ссылке.
Указатель на $value возможен, только если на перебираемый массив можно ссылаться (т.е. если он является переменной). Следующий код не будет работать:
Оператор foreach не поддерживает возможность подавления сообщений об ошибках с помощью префикса ‘@’.
Вы могли заметить, что следующие конструкции функционально идентичны:
Следующие конструкции также функционально идентичны:
Вот еще несколько примеров, демонстрирующие использование оператора:
/* Пример 1: только значение */
/* Пример 2: значение (для иллюстрации массив выводится в виде значения с ключом) */
$i = 0 ; /* только для пояснения */
/* Пример 3: ключ и значение */
/* Пример 4: многомерные массивы */
$a = array();
$a [ 0 ][ 0 ] = «a» ;
$a [ 0 ][ 1 ] = «b» ;
$a [ 1 ][ 0 ] = «y» ;
$a [ 1 ][ 1 ] = «z» ;
/* Пример 5: динамические массивы */
Распаковка вложенных массивов с помощью list()
В PHP 5.5 была добавлена возможность обхода массива массивов с распаковкой вложенного массива в переменные цикла, передав list() в качестве значения.
Результат выполнения данного примера:
Результат выполнения данного примера:
Как определить первую и последнюю итерации в цикле foreach?
вопрос простой. У меня есть foreach цикл в моем коде:
в этом цикле я хочу реагировать по-разному, когда мы находимся в первой или последней итерации.
18 ответов:
вы можете использовать счетчик:
Если вы предпочитаете решение, которое не требует инициализации счетчика вне цикла, я предлагаю сравнить текущий ключ итерации с функцией, которая сообщает вам последний / первый ключ массива.
это становится несколько более эффективным (и более читаемым) с предстоящим PHP 7.3.
решение для PHP 7.3 и выше:
решение для всех версий PHP:
, чтобы найти последний элемент, я нашел этот кусок кода работает каждый раз:
более упрощенная версия выше и если вы не используете пользовательские индексы.
Версия 2-потому что я пришел к ненависти, используя остальное, если это не необходимо.
вы можете удалить первый и последний элементы из массива и обрабатывать их отдельно.
Вот так:
удаление всего форматирования в CSS вместо встроенных тегов улучшит ваш код и ускорит время загрузки.
вы также можете избежать смешивания HTML с логикой php, когда это возможно.
Ваша страница может быть сделана намного более читаемой и поддерживаемой, разделяя такие вещи:
1: Почему бы не использовать простой for заявление? Предполагая, что вы используете реальный массив, а не Iterator вы можете легко проверить, является ли переменная счетчика 0 или одним меньше, чем все количество элементов. На мой взгляд это самое чистое и понятное решение.
2: Вы должны рассмотреть, используя Вложенные Наборы для хранения структуры дерева. Кроме того, вы можете улучшить все это с помощью рекурсивной функции.
для сценариев генерации SQL-запросов или всего, что выполняет другое действие для первого или последнего элемента, это намного быстрее (почти в два раза быстрее), чтобы избежать использования ненужных проверок переменных.
текущее принятое решение использует цикл и проверку в цикле, которая будет выполнена every_single_iteration, правильный (быстрый) способ сделать это следующим образом :
небольшой самодельный бенчмарк показал следующее:
тест1: 100000 работает модели морг
время: 1869.3430423737 миллисекунд
test2: 100000 запусков модели, если последний
время: 3235.6359958649 миллисекунд
и таким образом совершенно ясно, что проверка стоит много, и, конечно, она становится еще хуже, чем больше переменных проверок вы добавляете 😉
The array_keys функцию можно использовать для того чтобы сделать эффективную работу ответа как foreach :
Я не делал бенчмаркинг на этом, но никакая логика не была добавлена к циклу, который был самым большим ударом по производительности, поэтому я подозреваю, что тесты, предоставленные с эффективным ответом, довольно близки.
если ты хочешь functionalize такого рода вещи, я замахнулся на такой функция iterateList здесь. Хотя, возможно, вы захотите проверить код gist, если вы очень обеспокоены об эффективности. Я не уверен, сколько накладных расходов вводит весь вызов функции.
и я предпочитаю использовать for петли вместо foreach если бы я собирался использовать счетчик, как это:
О тонкостях работы foreach в PHP
В недавнем дайджесте интересных ссылок о PHP я обнаружил ссылку на комментарий Никиты Попова на StackOverflow, где он подробно рассказывает о механизме «под капотом» управляющей конструкции foreach.
Поскольку foreach действительно иногда работает более, чем странным образом, я счел полезным сделать перевод этого ответа.
Внимание: этот текст подразумевает наличие базовых знаний о функциональности zval’ов в PHP, в частности вы должны знать что такое refcount и is_ref.
foreach работает с сущностями разных типов: с массивами, с простыми объектами (где перечисляются доступные свойства) и с Traversable-объектами (вернее, объектами, у которых определен внутренний обработчик get_iterator). Здесь мы, в основном, говорим о массивах, но я скажу и об остальных в самом конце.
Прежде чем приступить, пара слов о массивах и их обходе, важная для понимания контекста.
Как работает обход массивов
Массивы в PHP являются упорядоченными хеш-таблицами (элементы хеша объединены в двусвязный список) и foreach обходит массив, следуя указанному порядку.
Таким образом, внешние указатели массива могут быть использованы только когда вы полностью уверены, что при обходе никакого пользовательского кода выполняться не будет. А такой код может оказаться в самом неожиданном месте, типа обработчика ошибок или деструктора. Вот почему в большинстве случаев PHP приходится использовать внутренний указатель вместо внешнего. Если бы это было иначе, PHP мог бы упасть из-за segmentation fault, как только пользователь начнет делать что-нибудь необычное.
Проблема внутреннего указателя в том, что он является частью HashTable. Так что, когда вы изменяете его, HashTable меняется вместе с ним. И коль скоро обращение к массивам в PHP делается по значению (а не по ссылке), вы вынуждены копировать массив, чтобы в цикле обходить его элементы.
Простой пример, показывающий важность копирования (кстати, не такая большая редкость), это вложенная итерация:
Здесь вы хотите чтобы оба цикла были независимым, а не хитро перебрасывались одним указателем.
Итак, мы дошли до foreach.
Обход массива в foreach
Теперь вы знаете, для чего foreach приходится создавать копию массива, прежде чем обойти его. Но это явно не вся история. Сделает PHP копию или нет, зависит от нескольких факторов:
Итак, это первая часть тайны: функция копирования. Вторая часть это то, как текущая итерация выполняется, и она тоже довольно странная. «Обычный» образец итерации, который вы уже знаете (и который часто используется в PHP — отдельно от foreach) выглядит примерно так (псевдокод):
итерация foreach выглядит немного иначе:
Такой режим работы foreach также является причиной, по которой внутренний указатель массива переходит к следующему элементу, если текущий удалён, а не к предыдущему (как вы могли бы ожидать). Всё сделано так, чтобы отлично работать с foreach (но, очевидно, со всем остальным будет работать не так хорошо, пропуская элементы).
Последствия для кода
Первое следствие вышеописанного поведения в том, что foreach копирует итерируемый массив в многих случаях (медленно). Но отриньте страх: я пробовал удалить требование копирования и не смог увидеть ускорения работы нигде, кроме искусственных бенчмарков (в которых итерация происходила в два раза быстрее). Похоже, люди просто не итерируют достаточно много.
Второе следствие в том, что обычно не должно быть других следствий. Поведение foreach, в основном, вполне понятно пользователю и просто работает как следует. Вас не должно волновать, как происходит копирование (и происходит ли оно вообще), и в какой конкретно момент времени перемещается указатель.
И третье следствие — и тут мы как раз подходим к вашим проблемам — в том, что иногда мы видим очень странное поведение, которое трудно понять. Это происходит конкретно тогда, когда вы пытаетесь модифицировать сам массив, который вы обходите в цикле.
Большую коллекцию поведения в пограничных случаях, которые появляются, когда вы модифицируете массив в ходе итерации, можно найти в тестах PHP. Вы можете начать с этого теста, после чего изменять 012 на 013 в адресе, и так далее. Вы увидите, как поведение foreach будет проявляться в разных ситуациях (всякие комбинации ссылок и.т.д.).
А сейчас вернёмся к вашим примерам:
Та же ситуация, что и в первом тесте.
Но эти примеры недостаточно убедительны. Поведение начинает быть по настоящему непредсказуемым, когда вы используете current в цикле:
Теперь попробуем сделать небольшое изменение:
Здесь у нас is_ref=1, так что массив не копирован (так как и выше). Но сейчас когда есть is_ref, массив больше не нужно разделять, передавая по ссылке к current. Теперь current и foreach работают с одним массивом. Вы видите массив сдвинутым на единицу как раз из-за того, как foreach обращается с указателем.
То же самое вы увидите, когда будете делать обход массива по ссылкам:
Еще одна небольшая вариация, здесь мы присвоим наш массив еще одной переменной:
Итерация объектов
При итерации объектов имеет смысл рассмотреть два случая:
Объект не Traversable (вернее, не определен внутренний обработчик get_iterator)
В этом случае итерация происходит почти так же, как у массивов. Та же семантика копирования. Единственное отличие: foreach запустит некий дополнительный код, чтобы пропустить свойства, недоступные в текущей области видимости. Еще пара интересных фактов:
Объект Traversable
В этом случае всё, что сказано выше, не будет применяться никоим образом. Также PHP не будет копировать и не будет применять никакие трюки вроде увеличения указателя до прохода цикла. Я думаю что режим прохода по обходимому (Traversable) объекту куда более предсказуем и не требует дальнейшего описания.
Замена итерируемого объекта во время цикла
Другой необычный случай, который я не упомянул — PHP допускает возможность замены итерируемого объекта во время цикла. Вы можете начать с одним массивом и продолжить, заменив его на полдороге другим. Или начать с массивом, в затем заменить его объектом:
Как видите, PHP просто начал обходить другую сущность, как только произошла замена.
Изменение внутреннего указателя массива во время итерации
Последняя деталь поведения foreach, которую я не упомянул (потому что может быть использована для получения по настоящему странного поведения): что может случиться если попытаться изменить внутренний указатель массива во время прохода цикла.
Тут вы можете получить не то, что ожидали: если вызывать next или prev в теле цикла (в случае передачи по ссылке), вы увидите, что внутренний указатель переместился, но это никак не повлияло на поведение итератора. Причина в том, что foreach делает бекап текущей позиции и хеша текущего элемента в HashPointer после каждого прохода цикла. На следующей проходе foreach проверит, не менялась ли позиция внутреннего указателя и попытается восстановить ее, используя этот хеш.
Давайте посмотрим что означает «попытается». Первый пример показывает, как изменение внутреннего указателя не меняет режим foreach:
Теперь давайте попробуем сделать unset элементу, к которому обратится foreach при первом проходе (ключ 1):
Тут вы увидите, что счетчик сброшен, так как не удалось найти элемент с подходящим хешом.
Имейте в виду, хеш — всего лишь хеш. Случаются коллизии. Попробуем теперь так:
Работает так, как мы и ожидали. Мы удалили ключ EzFY (тот, где как раз был foreach), так что был сделан сброс. Также мы добавили дополнительный ключ, поэтому в конце мы видим 4.
И вот тут приходит неведомое. Что произойдёт, если заменить ключ FYFY с FYFZ? Давайте попробуем:
Сейчас цикл перешёл непосредственно к новому элементу, пропуская всё остальное. Это потому что ключ FYFY имеет коллизию с EzFY (вообще-то, все ключи из этого массива тоже). Более этого, элемент FYFY находится по тому же адресу в памяти, что и элемент EzFY который только что был удален. Так что для PHP это будет та же самая позиция с тем же хешом. Позиция «восстановлена» и происходит переход к концу массива.
Определить первую и последнюю итерацию в цикле foreach в PHP?
Учитывая массив элементов и задача состоит в том, чтобы определить первую и последнюю итерацию в цикле foreach. Есть много способов решить эту проблему, которые перечислены ниже:
Метод 1: Наивный метод внутри цикла foreach — найти итерацию. Используйте переменную счетчика и проверьте, когда значение счетчика равно нулю, то это первая итерация, а когда значение счетчика равно длине 1, то это последняя итерация.
Пример:
// PHP программа для получения первой и
// последняя итерация
// Объявляем массив и инициализируем его
$myarray = array ( 1, 2, 3, 4, 5, 6 );
// Объявляем переменную счетчика и
// инициализируем его 0
// Отсюда начинается цикл
// Проверяем условие, если count равно 0, то
// это первая итерация
// Распечатать содержимое массива
print ( «: First iteration \n» );
// тогда это последняя итерация
// Распечатать содержимое массива
print ( «: Last iteration» );
Способ 2. Использование функции сброса и завершения для поиска первой и последней итерации. Функция reset () — это встроенная функция в PHP, которая принимает имя массива в качестве аргумента и возвращает первый элемент массива. Функция end () — это встроенная функция в PHP, которая принимает имя массива в качестве аргумента и возвращает его последний элемент.
Пример:
// PHP программа для получения первой и
// последняя итерация
// Объявление и массив и инициализация
$myarray = array ( 1, 2, 3, 4, 5, 6 );
// цикл начинается здесь
// Проверяем элемент и возвращаемое значение
// функция reset () равна
// будет первая итерация
// Описать элемент массива
print ( «: First iteration \n» );
// Проверить, если элемент и вернуть значение
// функция end () равна
// будет последняя итерация
// Описать элемент массива
print ( «: :ast iteration \n» );
Пример:
// PHP программа для получения первой и
// последняя итерация
// Объявляем массив и инициализируем его
$myarray = array ( 1, 2, 3, 4, 5, 6 );
// Присваиваем первый элемент массива переменной
How to determine the first and last iteration in a foreach loop?
The question is simple. I have a foreach loop in my code:
In this loop, I want to react differently when we are in first or last iteration.
20 Answers 20
If you prefer a solution that does not require the initialization of the counter outside the loop, then you can compare the current iteration key against the function that tells you the last / first key of the array.
PHP 7.3 and newer:
PHP 7.2 and older:
You could use a counter:
To find the last item, I find this piece of code works every time:
A more simplified version of the above and presuming you’re not using custom indexes.
You could remove the first and last elements off the array and process them separately.
Removing all the formatting to CSS instead of inline tags would improve your code and speed up load time.
You could also avoid mixing HTML with php logic whenever possible.
Your page could be made a lot more readable and maintainable by separating things like this:
Thank you @billynoah for your sorting out the end issue.
An attempt to find the first would be:
1: Why not use a simple for statement? Assuming you’re using a real array and not an Iterator you could easily check whether the counter variable is 0 or one less than the whole number of elements. In my opinion this is the most clean and understandable solution.
2: You should consider using Nested Sets to store your tree structure. Additionally you can improve the whole thing by using recursive functions.
The array_keys function can be used to make the efficient answer work like foreach :
I haven’t done benchmarking on this, but no logic has been added to the loop, which is were the biggest hit to performance happens, so I’d suspect that the benchmarks provided with the efficient answer are pretty close.
If you wanted to functionalize this kind of thing, I’ve taken a swing at such an iterateList function here. Although, you might want to benchmark the gist code if you’re super concerned about efficiency. I’m not sure how much overhead all the function invocation introduces.