Warning: Cannot use a scalar value as an array in /home/admin/public_html/forum/include/fm.class.php on line 757

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/include/fm.class.php on line 770

Warning: Invalid argument supplied for foreach() in /home/admin/public_html/forum/topic.php on line 737
Форумы портала PHP.SU :: Перебор глобального массива в цикле foreach в рекурсии

 PHP.SU

Программирование на PHP, MySQL и другие веб-технологии
PHP.SU Портал     На главную страницу форума Главная     Помощь Помощь     Поиск Поиск     Поиск Яндекс Поиск Яндекс     Вакансии  Пользователи Пользователи


 Страниц (1): [1]   

> Описание: Перебор глобального массива в цикле foreach в рекурсии.
Uchkuma
Отправлено: 31 Марта, 2010 - 09:12:44
Post Id



Участник


Покинул форум
Сообщений всего: 1539
Дата рег-ции: Март 2010  
Откуда: Киров


Помог: 6 раз(а)




Есть двумерный массив $items с элементами.
PHP:
скопировать код в буфер обмена
  1.  
  2. $items = array(
  3. array('id'=>1, 'sub'=>0, 'title'=>'Первый'),
  4. array('id'=>2, 'sub'=>0, 'title'=>'Второй'),
  5. array('id'=>3, 'sub'=>1, 'title'=>'Третий'),
  6. array('id'=>4, 'sub'=>1, 'title'=>'Четвертый'),
  7. array('id'=>5, 'sub'=>2, 'title'=>'Пятый'),
  8. array('id'=>6, 'sub'=>2, 'title'=>'Шестой')
  9. );
  10.  

Необходимо построить дерево элементов.
Делаю это так:
PHP:
скопировать код в буфер обмена
  1.  
  2. function items($sub=0){
  3. global $items;
  4.  
  5. $out = '<ul>';
  6.  
  7. foreach($items as $item){
  8. if($item['sub']!=$sub) continue;
  9.  
  10. $out .= '<li>'.$item['title'].'</li>';
  11.  
  12. $out .= items($item['id']);
  13. }
  14.  
  15. $out .= '</ul>';
  16.  
  17. return $out;
  18. }
  19.  
  20. echo items();
  21.  

На Денвере с PHP5 все выводится как и должно:
- Первый
    - Третий
    - Четвертый

- Второй
    - Пятый
    - Шестой

На сервере же (PHP 4.4.9) выводится только по одному первому элементу каждого уровня:
- Первый
    - Третий
    - Четвертый

Проблему решил добавив массив $items в параметр функции items() вместо его глобализации:
PHP:
скопировать код в буфер обмена
  1.  
  2. function items($items, $sub=0){
  3.  
  4. $out = '<ul>';
  5.  
  6. foreach($items as $item){
  7. if($item['sub']!=$sub) continue;
  8.  
  9. $out .= '<li>'.$item['title'].'</li>';
  10.  
  11. $out .= items($items, $item['id']);
  12. }
  13.  
  14. $out .= '</ul>';
  15.  
  16. return $out;
  17. }
  18.  
  19. echo items($items);
  20.  

Теперь и на Денвере и на сервере корректно выводятся все элементы:
- Первый
    - Третий
    - Четвертый

- Второй
    - Пятый
    - Шестой

Вопрос в том, почему так происходит?
Есть подозрение, что дело во внутреннем указателе массива $items, но насколько я знаю, foreach использует копию массива (т.е. не вносит изменений в исходный массив) и сбрасывает внутренний указатель массива на начало перед обходом.

(Отредактировано автором: 31 Марта, 2010 - 09:28:16)

 
 Top
jfr
Отправлено: 31 Марта, 2010 - 09:24:10
Post Id



Посетитель


Покинул форум
Сообщений всего: 332
Дата рег-ции: Март 2010  
Откуда: Таджикистан, Худжанд


Помог: 0 раз(а)

[+]


странно, но в PHP 4.4.9 вроде бы должен работать первоначальный ...
 
 Top
Champion Супермодератор
Отправлено: 31 Марта, 2010 - 09:24:27
Post Id



Активный участник


Покинул форум
Сообщений всего: 4350
Дата рег-ции: Авг. 2008  
Откуда: Москва


Помог: 57 раз(а)




Так сходу не видно. А попробуй в функции сделать var_dump($items). Будет различаться в 5 и в 4 PHP?
 
 Top
JustUserR
Отправлено: 31 Марта, 2010 - 09:26:35
Post Id



Активный участник


Покинул форум
Сообщений всего: 8715
Дата рег-ции: Июнь 2009  


Помог: 17 раз(а)




Uchkuma пишет:
Есть подозрение, что дело во внутреннем указателе массива $items
Скорее всего так - это можно проверить если заменить foreach на обычный цикл со счетчиком и явным перебором элементов
Конкретно дело в том что видимо ранее глобальные элементы передавались по ссылке и счетчик для них был общий - как вариант можно было при входе сохранять позицию с помощью current и при выходе выставлять ее на сохраненную
Ваше же решение просто копирует массив и у него появляется свой независимый счетчик


-----
Сделать можно все что угодно - нужно только старание, терпение и хороший поисковик Улыбка
Безлимитный web-хостинг от 15 рублей за 40 МБ дискового пространства - http://ihost[dot]oks71[dot]ru/
 
 Top
Uchkuma
Отправлено: 31 Марта, 2010 - 16:00:36
Post Id



Участник


Покинул форум
Сообщений всего: 1539
Дата рег-ции: Март 2010  
Откуда: Киров


Помог: 6 раз(а)




JustUserR, я так и подумал. В каждой последующей рекурсии items() перебор массива $items начинается не с первого элемента, и после n-ной рекурсии, когда последний элемент массива был достигнут, циклы foreach предыдущих рекурсий тоже завершаются.

Я так понимаю, мое решение является нерациональным, т.к. $items будет каждый раз копироваться и это приведет к излишнему потреблению памяти? Особенно если очень много элементов в массиве?
JustUserR пишет:

как вариант можно было при входе сохранять позицию с помощью current и при выходе выставлять ее на сохраненную

Насколько я знаю, current() возвращает значение текущего элемента. Не понимаю, как с помощью него можно сохранить позицию? Может вы имели ввиду функцию key()?
Если я с помощью key() сохраню индекс последнего обработанного элемента перед следующей рекурсией, то как после нее с помощью этого индекса вернуть указатель на этот элемент?
В принципе, я представляю как это все можно сделать при помощи for со счетчиком, но я стараюсь им не пользоваться для перебора массивов без особой необходимости, тем более, что для этой цели предназначен foreach.
 
 Top
Ch_chov
Отправлено: 31 Марта, 2010 - 16:33:20
Post Id



Постоянный участник


Покинул форум
Сообщений всего: 2121
Дата рег-ции: Июль 2008  
Откуда: из города


Помог: 90 раз(а)




Попробуй передавать по ссылке
PHP:
скопировать код в буфер обмена
  1. function items(&$items, $sub=0) {
  2. ...
  3. }


Цитата:
В принципе, я представляю как это все можно сделать при помощи for со счетчиком, но я стараюсь им не пользоваться для перебора массивов без особой необходимости, тем более, что для этой цели предназначен foreach.

А for с массивами вроде даже быстрей работает чем foreach.

(Отредактировано автором: 31 Марта, 2010 - 16:33:58)

 
 Top
JustUserR
Отправлено: 31 Марта, 2010 - 16:36:22
Post Id



Активный участник


Покинул форум
Сообщений всего: 8715
Дата рег-ции: Июнь 2009  


Помог: 17 раз(а)




Uchkuma пишет:
JustUserR, я так и подумал. В каждой последующей рекурсии items() перебор массива $items начинается не с первого элемента, и после n-ной рекурсии, когда последний элемент массива был достигнут, циклы foreach предыдущих рекурсий тоже завершаются.
Я так понимаю, мое решение является нерациональным, т.к. $items будет каждый раз копироваться и это приведет к излишнему потреблению памяти? Особенно если очень много элементов в массиве?
Все совершенно верно говорите
Вероятно ваш способ будет работать оптимально если попробовать передавать массив по ссылке с помощью & - хотя по смысло это схоже с проблемной ситуацие но реализация вероятно без бага
Uchkuma пишет:
Насколько я знаю, current() возвращает значение текущего элемента. Не понимаю, как с помощью него можно сохранить позицию? Может вы имели ввиду функцию key()?
Если я с помощью key() сохраню индекс последнего обработанного элемента перед следующей рекурсией, то как после нее с помощью этого индекса вернуть указатель на этот элемент?
Все делается достаточно просто - ведь проблема в том что вложенная рекурсивная функция меняет счетчик у внешней функции - значит необьходимо при входе на шаг рекурсии сохранить позицию счетчика (Она будет показывать позицию счетчика у рекурсивной функции более верхнего уровня) потом сбросить его и провести свой цикл и вернуть индекс обратно - таким образом после завершения шага рекурсии вызывавшая функция продолжит цикл с нужного места

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


-----
Сделать можно все что угодно - нужно только старание, терпение и хороший поисковик Улыбка
Безлимитный web-хостинг от 15 рублей за 40 МБ дискового пространства - http://ihost[dot]oks71[dot]ru/
 
 Top
Uchkuma
Отправлено: 31 Марта, 2010 - 21:52:18
Post Id



Участник


Покинул форум
Сообщений всего: 1539
Дата рег-ции: Март 2010  
Откуда: Киров


Помог: 6 раз(а)




JustUserR пишет:

значит необьходимо при входе на шаг рекурсии сохранить позицию счетчика (Она будет показывать позицию счетчика у рекурсивной функции более верхнего уровня) потом сбросить его и провести свой цикл и вернуть индекс обратно

Что-то я не догоняю :( Как же все-таки вернуть индекс обратно? Что-то у меня никак не вырисовывается картина. JustUserR, можно ли поподробнее?

Придумал еще один банальный способ, не добавляя массив в параметры функции. В первом варианте добавил после глобализации строчку $items_copy = $items; и в цикле уже работаем с $items_copy. Но это опять же плодит массивы до неизвестного количества, а хотелось бы работать с одним массивом оперируя лишь его указателем.

По поводу:
JustUserR пишет:

Вероятно ваш способ будет работать оптимально если попробовать передавать массив по ссылке с помощью & - хотя по смысло это схоже с проблемной ситуацие но реализация вероятно без бага

однако нет. Ситуация аналогичная. В PHP5 работает, в PHP 4.4.9 - нет.
Ch_chov пишет:
А for с массивами вроде даже быстрей работает чем foreach.

Возможно, но не всегда. foreach перебирает элементы массива по порядку, а for по счетчику, т.е. по индексу. А если в массиве элементы начинаются не с нуля или есть пропуски (некоторые элементы были unset)? А если ключи строковые, а не числовые? Тогда прибегаем к использованию еще одной функции - each(). Неужели использование двух функций будет работать быстрее чем одна? Но это уже оффтоп... Простите, for это не функция ))

(Отредактировано автором: 31 Марта, 2010 - 22:11:22)

 
 Top
Champion Супермодератор
Отправлено: 01 Апреля, 2010 - 09:53:42
Post Id



Активный участник


Покинул форум
Сообщений всего: 4350
Дата рег-ции: Авг. 2008  
Откуда: Москва


Помог: 57 раз(а)




Uchkuma пишет:
В принципе, я представляю как это все можно сделать при помощи for со счетчиком, но я стараюсь им не пользоваться для перебора массивов без особой необходимости, тем более, что для этой цели предназначен foreach.
Для этой цели предназначен for. foreach преднезначен для тех случаев, когда ты не знаешь ключи массива. И да, foreach работает медленне.
 
 Top
Uchkuma
Отправлено: 01 Апреля, 2010 - 10:31:57
Post Id



Участник


Покинул форум
Сообщений всего: 1539
Дата рег-ции: Март 2010  
Откуда: Киров


Помог: 6 раз(а)




Champion пишет:
Для этой цели предназначен for. foreach преднезначен для тех случаев, когда ты не знаешь ключи массива.

Так и есть, я могу не знать ключи массива.
 
 Top
JustUserR
Отправлено: 01 Апреля, 2010 - 17:48:25
Post Id



Активный участник


Покинул форум
Сообщений всего: 8715
Дата рег-ции: Июнь 2009  


Помог: 17 раз(а)




Uchkuma пишет:
Так и есть, я могу не знать ключи массива.
Вообще в любом случае ключи массива можно получить с помощью фукнции http://www.php.su/functions/?array_keys
Uchkuma пишет:
Что-то я не догоняю Недовольство, огорчение Как же все-таки вернуть индекс обратно? Что-то у меня никак не вырисовывается картина. JustUserR, можно ли поподробнее?
Как вариант придется вводить специальные счетчики для сохранения текущей позиции и перевода номера текущего элемента с помощью reset/next
Однако лучше использовать простой цикл $for и вышеприведенную функцию для получения списка ключей
Uchkuma пишет:
Придумал еще один банальный способ, не добавляя массив в параметры функции. В первом варианте добавил после глобализации строчку $items_copy = $items; и в цикле уже работаем с $items_copy. Но это опять же плодит массивы до неизвестного количества, а хотелось бы работать с одним массивом оперируя лишь его указателем
Хм но ведь можно проверять уровень сложенности рекурсии (В вашем случае по номеру родительского элемента в массиве) и во всех внутренних шагах рекурсии уже не призводить данное копирование


-----
Сделать можно все что угодно - нужно только старание, терпение и хороший поисковик Улыбка
Безлимитный web-хостинг от 15 рублей за 40 МБ дискового пространства - http://ihost[dot]oks71[dot]ru/
 
 Top
Uchkuma
Отправлено: 01 Апреля, 2010 - 21:38:20
Post Id



Участник


Покинул форум
Сообщений всего: 1539
Дата рег-ции: Март 2010  
Откуда: Киров


Помог: 6 раз(а)




JustUserR, спасибо! Действительно, самое рациональное решение с array_keys() и циклом for. Теперь мы не зависим от внутреннего указателя массива, массив лишний раз не копируем и нам не нужны никакие специальные счетчики текущей позиции!
PHP:
скопировать код в буфер обмена
  1. function items($sub=0){
  2. global $items, $items_keys;
  3.  
  4. $out = '<ul>';
  5.  
  6. for($i=0; $i<count($items_keys); $i++){
  7. $item = $items[$items_keys[$i]];
  8. if($item['sub']!=$sub) continue;
  9.  
  10. $out .= '<li>'.$item['title'].'</li>';
  11.  
  12. $out .= items($item['id']);
  13. }
  14.  
  15. $out .= '</ul>';
  16.  
  17. return $out;
  18. }
  19.  
  20. $items_keys = array_keys($items);
  21. echo items();

Тема закрыта!
 
 Top
JustUserR
Отправлено: 02 Апреля, 2010 - 16:58:36
Post Id



Активный участник


Покинул форум
Сообщений всего: 8715
Дата рег-ции: Июнь 2009  


Помог: 17 раз(а)




Uchkuma пишет:
JustUserR, спасибо! Действительно, самое рациональное решение с array_keys() и циклом for. Теперь мы не зависим от внутреннего указателя массива, массив лишний раз не копируем и нам не нужны никакие специальные счетчики текущей позиции!
Пожалуйста! В PHP имеется очент много встроенных функций для работы с массивами поэтому для большинсва задач уже есть готовая функция или их комбинация Улыбка


-----
Сделать можно все что угодно - нужно только старание, терпение и хороший поисковик Улыбка
Безлимитный web-хостинг от 15 рублей за 40 МБ дискового пространства - http://ihost[dot]oks71[dot]ru/
 
 Top
skeef
Отправлено: 11 Июля, 2015 - 13:14:04
Post Id


Новичок


Покинул форум
Сообщений всего: 2
Дата рег-ции: Июль 2015  


Помог: 0 раз(а)




Ну уж если совсем рационально ;)

PHP:
скопировать код в буфер обмена
  1.  
  2. . . .
  3. $cnt = count($items_keys);
  4.  
  5. for($i=0; $i<$cnt; $i++){
  6. . . .
  7.  
 
 Top
Страниц (1): [1]
Сейчас эту тему просматривают: 0 (гостей: 0, зарегистрированных: 0)
« Программирование на PHP »


Все гости форума могут просматривать этот раздел.
Только зарегистрированные пользователи могут создавать новые темы в этом разделе.
Только зарегистрированные пользователи могут отвечать на сообщения в этом разделе.
 



Powered by PHP  Powered By MySQL  Powered by Nginx  Valid CSS  RSS

 
Powered by ExBB FM 1.0 RC1. InvisionExBB