Strange behavior of unset in foreach by reference loop - php

I know one should not modify physical structure of array while looping by reference, but I need explanation of what is going on in my code. Here we go:
$x= [[0],[1],[2],[3],[4]];
foreach ($x as $i => &$upper) {
print $i;
foreach ($x as $j => &$lower) {
if($i == 0 && $j == 2) {
unset($x[2]);
} else if($i == 1 && $j == 3) {
unset($x[3]);
}
}
}
The output is 01. Surprising that outer loop iterates only twice, for indices 0 and 1. I was expecting the output to be 014.
I have read lots of blog posts and questions about hazards of using array references, but nothing that can explain this phenomenon. I am breaking my head over it for hours now.
EDIT:
The code above is the minimal reproducible code. One explanation (but an incorrect one) that might seem to be the case is this:
The outer loop goes through two iterations before the internal pointer is set to index 2. But the loop does not find any element at index 2 and thus thinks no elements are left and quits.
The problem with this theory is it doesn't quite explain this code:
$x= [[0],[1],[2],[3],[4]];
foreach ($x as $i => &$upper) {
print $i;
foreach ($x as $j => &$lower) {
if($i == 0 && $j == 2) {
unset($x[2]);
// No if else here
unset($x[3]);
}
}
}
By the same token, the above code should also produce 01, but its actual output is 014, as expected. Even when two items in a series are removed, php knows that are still elements left to be iterated over. Could this possibly be a bug with php scripting engine?

A simple code to reproduce your issue:
$x = [0, 1, 2];
foreach ($x as $k => &$v) {
print $k;
if ($k == 0) {
unset($x[1]);
}
end($x); // move IAP to end
next($x); // move IAP past end (that's the same as foreach ($x as $y) {} would do)
}
If you foreach over an array, it's copied (= no problem when iterating, you'll iterate over the full original array).
But if you foreach by reference, the array is not copied (the reference needs to match the original array, so copying impossible).
Foreach internally always saves the position of the next element to iterate over.
But when the next position of an array is removed, foreach needs to go back to the array and check it's internal array pointer (IAP).
In this case the next position is destroyed and the IAP is past the end, it ends the loop.
That's what you're seeing here.
Also interesting: hhvm has a different behaviour to php here: http://3v4l.org/81rl8
Addendum: The infinite foreach loop:
$x = [0,1,2];
foreach ($x as $k => &$v) {
print $k;
if ($k == 1) {
unset($x[2]);
} else {
$x[2] = 1;
}
reset($x);
}
If you understood my explanations above, guess why that loops indefinitely.

Related

How to remove an all the elements in an array through iteration

I have an array $scripts_stack = []; that holds arrays:
$array_item = array('script' => $file_parts, 'handle' => $file_name, 'src' => $url);
array_push($scripts_stack, $array_item);
<?php
for ($i = 0; $i < sizeof($scripts_stack); $i++) {
$child_array = $scripts_stack[$i];
if (is_array($child_array)) {
// Do things with $child_array,
// then remove the child array from $scripts_stack when done with (BELOW)
unset($scripts_stack[$i]);
}
}
echo "Array Size : " . (sizeof($scripts_stack)); // AT THE END
However, my attemts only remove half the elements. No matter what I try, it's only half the items that get removed. sizeof($scripts_stack) is always half the size of what it was at the start.
I'm expecting that it would be empty // AT THE END
Why is it that I only get half the elements in the array removed?
Thank you all in advance.
As mentioned in other answers, $i increments but the sizeof() the array shrinks. foreach() is probably the most flexible looping for arrays as it exposes the actual key (instead of hoping it starts at 0 and increments by 1) and the value:
foreach ($scripts_stack as $key => $child_array) {
if (is_array($child_array)) {
// Do things with $child_array,
// then remove the child array from $scripts_stack when done with (BELOW)
unset($scripts_stack[$key]);
}
}
Just FYI, the way you're doing it with for almost works. You just need to establish the count before the loop definition, rather than recounting in the continuation condition.
$count = sizeof($scripts_stack);
for ($i = 0; $i < $count; $i++) { // ...
Still, I think it would be better to just use a different type of loop as shown in the other answers. I'd personally go for foreach since it should always iterate every element even if some indexes aren't present. (With the way you're building the array, it looks like the indexes should always be sequential, though.)
Another possibility is to shift elements off of the array rather than explicitly unsetting them.
while ($child_array = array_shift($scripts_stack)) {
// Do things with $child_array,
}
This will definitely remove every element from the array, though. It looks like $child_array should always be an array, so the is_array($child_array) may not be necessary, but if there's more to it that we're not seeing here, and there are some non-array elements that you need to keep, then this won't work.
You advanced $i while the array is getting shrinked, but in the same time you jump over items in your array.
The first loop is where $i == 0, and then when you removed item 0 in your array, the item that was in the second place has moved to the first place, and your $i == (so you will not remove the item in the current first place, and so on.
What you can do is use while instead of for loop:
<?php
$i = 0;
while ($i < sizeof($scripts_stack)) {
$child_array = $scripts_stack[$i];
if (is_array($child_array)) {
// Do things with $child_array,
// then remove the child array from $scripts_stack when done with (BELOW)
unset($scripts_stack[$i]);
} else {
$i++;
}
}
echo "Array Size : " . (sizeof($scripts_stack)); // AT THE END
May be you can use this script.It's not tested.
foreach($array as $key => $value ) {
unset($array[$key]);
echo $value." element is deleted from your array</br>";
}
I hope , it will help you.
The problem root is in comparing $i with sizeof($scripts_stack). Every step further sizeof($scripts_stack) becomes lower (it calculates at every step) and $i becomes higher.
The workaround may look like this:
<?php
$scripts_stack = [];
$array_item = array('script' => 1, 'handle' => 2, 'src' => 3);
array_push($scripts_stack, $array_item);
array_push($scripts_stack, $array_item);
array_push($scripts_stack, $array_item);
array_push($scripts_stack, $array_item);
array_push($scripts_stack, $array_item);
while (sizeof($scripts_stack) > 0) {
$child_array = array_shift($scripts_stack);
if (is_array($child_array)) {
// Do things with $child_array,
// then remove the child array from $scripts_stack when done with (BELOW)
}
}
echo "Array Size : " . (sizeof($scripts_stack)); // AT THE END
https://3v4l.org/N2p3v

Stop loop and continue from stopping point

problem Solved..... thanks alot
I want to break the loop and continue loop from the point that the loop stop. E.g., if I have array like this:
$arr = array('1', '2', '3', '4', '5');
I want to stop looping on number '3' and take an action and continue from '4'. I tried this code:
$x = 0;
foreach($arr as $key){
if($x == 3) break; // here I stop loop in number 3
echo $key;
$x++;
// I want to continue loop from 4
}
why stop?
use
$x = 0;
foreach($arr as $key){
if($x == 3)
doSomething();
else
echo $key;
$x++;
}
it will continue with iteration 4, after "doing Something". (Correctly spoken: It will iterate from 1 to n, and only perform an action, when $x==3, otherwhise print the key.)
If you just want to avoid key "3" beeing printed, you can use the continue statement:
$x = 0;
foreach($arr as $key){
if($x++ == 3)
continue; //proceed with next iteration
echo $key;
}
but then you need to use $x++ in your comparrision, otherwhise it will get stuck at $x==3, cause the increment will always be skipped.
Sidenode: If you NEED $x to be the correct line number, use a for() instead of foreach() - use foreach(), if you dont care about the actual line number, but need to process ALL entries within an array.
Sidenode 2: foreach($arr as $key) is wrong. This expression will give you the value for each array entry, not the key. use foreach($arr as $key=>$value) or foreach($arr as $value) to have a correct name on the variable(s).

PHP, continue; on foreach(){ foreach(){

Is there a way to continue on external foreach in case that the internal foreach meet some statement ?
In example
foreach($c as $v)
{
foreach($v as $j)
{
if($j = 1)
{
continue; // But not the internal foreach. the external;
}
}
}
Try this, should work:
continue 2;
From the PHP Manual:
Continue accepts an optional numeric argument which tells it how many levels of enclosing loops it should skip to the end of.
here in the examples (2nd exactly) described code you need
Try this: continue 2; According to manual:
continue accepts an optional numeric argument which tells it how many levels of enclosing loops it should skip to the end of.
There are two solutions available for this situation, either use break or continue 2. Note that when using break to break out of the internal loop any code after the inner loop will still be executed.
foreach($c as $v)
{
foreach($v as $j)
{
if($j = 1)
{
break;
}
}
echo "This line will be printed";
}
The other solution is to use continue followed with how many levels back to continue from.
foreach($c as $v)
{
foreach($v as $j)
{
if($j = 1)
{
continue 2;
}
}
// This code will not be reached.
}
This will continue to levels above (so the outer foreach)
continue 2
<?php
foreach($c as $v)
{
foreach($v as $j)
{
if($j = 1)
{
continue 2; // note the number 2
}
}
}
?>
RTM
Try break instead of continue.
You can follow break with an integer, giving the number of loops to break out of.
you have to use break instead of continue, if I get you right
Here I wrote an explanation on the matter: What is meant by a number after "break" or "continue" in PHP?

foreach and for loop in PHP

am working on php loops and i find myself that i need to write a nested loop bur when i try to combine foreach and for loop it yield to unexpected result
Here are my codes
foreach ($district_ward as $key => $value) {
$ward_ids = array_keys($district_ward[$key]);
echo $key;
for ($x = 0; $x < count($ward_ids)-1; $x++) {
$district_village[$key]= array_merge($value[$ward_ids[$x]], $value[$ward_ids[$x+1]]);
}
}
This gives me this
347
but when i print the value of $key within the for loop, that is
foreach ($district_ward as $key => $value) {
$ward_ids = array_keys($district_ward[$key]);
for ($x = 0; $x < count($ward_ids)-1; $x++) {
echo $key;
$district_village[$key]= array_merge($value[$ward_ids[$x]], $value[$ward_ids[$x+1]]);
}
}
i get this
3
I'm just going to guess that your 2nd and 3rd array are only one entry long. The condition for the loop specifies $x < count($ward_ids) - 1. If your array only has one entry, one - 1 will make it loop 0 times. In other words, your inner loop is not executed at all.
Are you printing this to a html page? Are your keys 3, 4 and 7? They would appear as 347 if you were echoing to an html page. Try
echo "$key<br />\n";
or even
echo "<pre>".print_r($district_ward)."</pre>";
before the loop to see what the array looks like.
foreach ($district_ward as $key => $value) {
$ward_ids = array_keys($district_ward[$key]);
for ($x = 0; $x < count($ward_ids); $x++) {
echo $key;
$district_village[$key]= array_merge($value[$ward_ids[$x]], $value[$ward_ids[$x+1]]);
}
}
Should do the trick, this because you start at 0 up UNTIL the amount of $ward_ids
Let's say your $ward_ids has 1 entry, then you would have looped 0 till 0, so no loops at all,
And lets say it had 102 entries, then you would have looped 1-101, leaving 102 out since you started at 0.

Break levels in PHP

I have this snippet of code and it's working just fine (because in my example that variable really exists in $arrival_time so it breaks at about $k = 10).
$arrival_time = explode(",", $arrival_timeAll[1]);
$sizeOfArrival = sizeof($arrival_time);
$k = -1;
while (++$k < $sizeOfArrival) {
if ($arrival_time[$k] >= $someVariable) {
break;
}
}
Isn't that the same as this code? I added the while(true) loop and increased the "break level" - so it's now break 2, not just break. But seems that's an infinite loop. Why?
while (true) {
$arrival_time = explode(",", $arrival_timeAll[1]);
$sizeOfArrival = sizeof($arrival_time);
$k = -1;
while (++$k < $sizeOfArrival) {
if ($arrival_time[$k] >= $someVariable) {
break 2;
}
}
}
Why adding while(true)? Because I need to define some more statements (which here aren't neccesary for explanation) if inside while loop doesn't find the matching one (if that "break" in first case, "break 2" in second case doesn't run).
Anyway - why this isn't working?
In some cases, the predicate for the inner while loop is never hit, thus the while loop is never executed. As you don't have a break after it, the loop will run forever. This case would be if the array is empty, 0 < 0 is false.
Starting with $k = -1, the first time the predicate gets evaluated, $k will be 0 as you are using the preincrement operator, for an empty array this will evaluate to false and the code with run infinite. Without the while(true) loop this wouldn't cause any problems as you'd just jump straight over it.

Categories