PHP while inside while - php

Let's say I'm trying to combine items from two lists, and I want to get this result:
A7
A8
B7
B8
This is my code:
<?php
$list1_array = array('A', 'B');
$list2_array = array('7', '8');
while(list( , $item1) = each($list1_array)) {
while(list( , $item2) = each($list2_array)) {
echo $item1.$item2."<br />";
}
}
?>
I get this result:
A7
A8
I seems like outside 'while' doesn't make the second loop?
What am I doing wrong?

While it might be better to use a slightly more common (perhaps more readable) approach (e.g. by using foreach loops as shown by GolezTrol,) in answer to your original questions:
The problem is most likely happening because the internal "cursor" (or "pointer") for your array is not being reset... so it never gets back to the start of the original array.
Instead, what if you try something like this:
<?php
$list1_array = array('A', 'B');
$list2_array = array('7', '8');
while(list(,$item1) = each($list1_array)) {
while(list(,$item2) = each($list2_array)) {
echo $item1.$item2."<br />";
}
reset($list2_array);
}
?>

Why not use foreach?
foreach ($list1_array as $item1)
{
foreach ($list2_array as $item2)
{
echo $item1.$item2."<br />";
}
}
Using a while loop with each makes the loop depend on the array pointer. An array has a pointer that tells you which item is the 'current' one. You can use functions like current to get the current item in the array. each is also such a function. It returns the current item (or actually an array with the key and value of the current item).
And therein lies the problem. The inner while loop stops when you are at the end of the array. So for the first item of the outer array (array1), the inner while loop (array2) runs fine. But the second time, the pointer is still at the end of the array and each returns false right away.
So, the solution could be to reset the array pointer, using the reset function as sharply pointed out by #summea. Or you can use a foreach loop, which is not affected by this fenomenon, because it resets the array pointer itself when it starts. Also, I this it's more readable, especially due to the weird list construct. Nevertheless, it might be good to know how the internals work, and your while loop works more low-level than foreach.

Related

Checking the first element of array, regardless of array indexes

I have a need to check if the elements in an array are objects or something else. So far I did it like this:
if((is_object($myArray[0]))) { ... }
However, on occasion situations dictate that the input array does not have indexes that start with zero (or aren't even numeric), therefore asking for $myArray[0] will generate a Notice, but will also return the wrong result in my condition if the first array element actually is an object (but under another index).
The only way I can think of doing here is a foreach loop where I would break out of it right on the first go.
foreach($myArray as $element) {
$areObjects = (is_object($element));
break;
}
if(($areObjects)) { ... }
But I am wondering if there is a faster code than this, because a foreach loop seems unnecessary here.
you can use reset() function to get first index data from array
if(is_object(reset($myArray))){
//do here
}
You could get an array of keys and get the first one:
$keys = array_keys($myArray);
if((is_object($myArray[$keys[0]]))) { ... }
try this
reset($myArray);
$firstElement = current($myArray);
current gets the element in the current index, therefore you should reset the pointer of the array to the first element using reset
http://php.net/manual/en/function.current.php
http://php.net/manual/en/function.reset.php

Unexpected behavior with references

I have some unexpected behavior with references:
foreach ($this->data as &$row)
{
$row['h'] = 1;
}
foreach ($this->data as $id => $row)
{
... in some cases $row[$id] = $row;
}
The result is that the last element of the array is replaced with the second to last element of the array. It is fixed with the following code:
foreach ($this->data as $key => $row)
{
$this->data[$key]['h'] = 1;
}
Unfortunately, I don't have more time to spend on this. Maybe it is an error with PHP (PHP 5.5.9-1ubuntu4) or something I don't know about references?
There is a perfectly logical explanation and this is not a bug!
PHP 5 introduces the possibility of modifying the contents of the array directly by assigning the value of each element to the iterated variable by reference rather than by value. Consider this code, for example:
$a = array (’zero’,’one’,’two’);
foreach ($a as &$v) {
}
foreach ($a as $v) {
}
print_r ($a);
It would be natural to think that, since this little script does nothing to the array, it will not affect its contents... but that’s not the case! In fact, the script provides the following output:
Array
(
[0] => zero
[1] => one
[2] => one
)
As you can see, the array has been changed, and the last key now contains the value ’one’. How is that possible? The first foreach loop does not make any change to the array, just as we would expect. However, it does cause $v to be assigned a reference to each of $a’s elements, so that, by the time the loop is over, $v is, in fact, a reference to $a[2].
As soon as the second loop starts, $v is now assigned the value of each element. However, $v is already a reference to $a[2]; therefore, any value assigned to it will be copied automatically into the last element of the arrays! Thus, during the first iteration, $a[2] will become zero, then one, and then one again, being effectively copied on to itself. To solve this problem, you should always unset the variables you use in your by-reference foreach loops—or, better yet, avoid using the former altogether.
When looping over an array by reference, you need to manually let go of the reference at the end of your for loop to avoid weird behaviors like this one. So your first foreach should be:
foreach ($this->data as &$row)
{
.... code ....
}
unset($row);
In this case, unset is only destroying the reference, not the contents referenced by $row.
See the warning in the PHP foreach documentation

Strange foreach loop after modifying array

As I wrote some code, PHP confused me a little as I didn't expected the result of the following code:
$data = array(array('test' => 'one'), array('test' => 'two'));
foreach($data as &$entry) {
$entry['test'] .= '+';
}
foreach($data as $entry) {
echo $entry['test']."\n";
}
I think it should output
one+
two+
However the result is: http://ideone.com/e5tCsi
one+
one+
Can anyone explain to me why?
This is expected behaviour, see also https://bugs.php.net/bug.php?id=29992.
The reference is maintained when using the second foreach, so when using the second foreach the value of $entry, which points still to $data[1], is overwritten with the first value.
P.s. (thanks to #billyonecan for saying it): you need to unset($entry) first, so that your reference is destroyed.
This is mentioned specifically in the documentation for foreach. You should unset the loop variable when it gets elements of the array by reference.
Warning
Reference of a $value and the last array element remain even after the
foreach loop. It is recommended to destroy it by unset().

Unsetting elements from array within foreach

I'm currently working on a generic form creation class and had an issue yesterday.
I made a snippet to reproduce the problem.
Essentially I want to delete elements that are grouped from the original elements array after the whole group has been drawn and I'm doing this while looping over the elements array.
The code snippet should cover the problem, am I missing something here? From my knowledge deleting an element while foreach is completely safe and legal since foreach internally only uses a copy that may be modified during the loop.
$ids = array('a' => array(), 'b' => array(), 'c' => array());
$groups['g1'] = array('a', 'c');
foreach($ids as $id => $element) {
//var_dump($ids);
$g_id = '';
// search the id in all groups
foreach($groups as $group_id => $group) {
if(in_array($id, $group)) {
$g_id = $group_id;
break;
}
}
// element is part of a group
if($g_id !== '') {
//echo $g_id;
// element a and c gets unset within loop and should not be in $ids anymore
foreach($groups[$g_id] as $field_id) {
unset($ids[$field_id]);
echo $field_id;
}
unset($groups[$g_id]);
} else {
if($id === 'a' || $id === 'c')
echo $id;
}
}
Element 'c' gets unset within the foreach(groups ..) loop but is afterwards again outputted in the else branch. Also when i var_dump($fields) at the beginning i always get 'a', 'b' and 'c' inside. I'm using PHP 5.4.7.
Thanks in advance
EDIT: i made a mistake in the sample code, its now updated. All comments about using the wrong index (it would have been 0,1 etc) were correct of course.
The values when using var_dump are unset now, but i still get into the else with 'c' one time.
EDIT2:
Im not done with the original code but after reading through the comments I currently came up with following solution to the posted code snippet above:
$ids=array("a"=>array(),"b"=>array(),"c"=>array(),"d"=>array(),"e"=>array());
$groups=array(array("a"),array("c", "e"));
array_walk($groups,function($v,$i)use(&$ids){
$in_both = array_intersect(array_keys($ids),$v);
//var_dump($in_both);
foreach($in_both as $b) {
unset($ids[$b]);
}
});
print_r($ids);
or
$ids=array("a"=>array(),"b"=>array(),"c"=>array(),"d"=>array(),"e"=>array());
$groups=array(array("a"),array("c"));
array_walk($ids,function($v,$i)use(&$ids, $groups){
$in_both = array();
foreach($groups as $g) {
if(in_array($i,$g)) {
$in_both = array_intersect(array_keys($ids),$g);
}
}
foreach($in_both as $b) {
unset($ids[$b]);
}
});
print_r($ids);
Using a foreach does not work for me in this case, because i need to change the $ids array while the loop is iterating over it.
In the very most basic situation a code something like this:
$ids = array('a', 'b');
while(count($ids)) {
array_pop($ids);
echo 'pop';
}
echo 'empty';
Allthough foreach can change the original values from the array it will not change the copy of the array used for the iteration as nl-x already stated.
Thanks to Passerby for the idea of using array_walk for this.
EDIT3:
Updated code snipped once more. The second snipped allthough behaves undefined as well. Deleting elements from an array while iterating over its seems to be a bad idea.
Chris, if I understand correctly, you don't expect 'C' to be outputted in the else branch?
But it should be outputted. Your logic is:
you do foreach ids and start with id 'a'.
then you clear ids a and c from ids and delete the group g1 that contained 'a'. During this step the deleted ids will be outputted, being a and c. (Clearing a and c from ids will have no impact on the foreach($ids as $id) as foreach will continue with the untouched copy even after ids array has been cleared.)
then you do id 'b': it is not found in any group. (actually, there isn't any group left by now anyway)
so for 'b' you enter the else branch. But the if() inside the else branch prevents output
then you do id 'c', which is also not found in any group, because you have already deleted group g1! There are no groups left, remember?
so for 'c' you also enter the else branch. And this time the if() inside the else branch allows the output! The output being just c
So the total output is indeed acc.
It is good to know that a foreach() that continues with a untouched copy even after its elements were cleared, is a specific PHP thing. Other language do no necessarily do the same.
Spent some time reading your code, and I guess your procedure is:
For every element in $ids, check if it exists in some sub-array in $groups;
If it exists, delete everything in $ids that also exists in this sub-array.
Following the above logic, I come up with this:
$ids=array("a","b","c","d","e");
$groups=array(array("a","c"),array("c","e"));
array_walk($groups,function($v,$i)use(&$ids){
$ids=array_diff($ids,$v);
});
print_r($ids);//debug
Live demo
I'm double checking now. But I think unsetting an array with foreach is not really safe.
What I usually would do is take a foreach, and start with the highest indexes and descrease the index along the way. for($i = count($arr)-1; $i >= 0; $i--) { unset($array[$i]); }
I'll edit this post in a few minutes.
edit: i was confused. The for with $i++ is indeed the culprit. foreach is safe (in php! not in all languages)
<?php
$arr = Array(1,2,3,4,5,6,7,8,9,10);
foreach ($arr as $key=>$val)
unset($arr[$key]);
echo implode(',',$arr); // returns nothing
$arr = Array(1,2,3,4,5,6,7,8,9,10);
for ($i=0; $i<count($arr); $i++)
unset($arr[$i]);
echo implode(',',$arr); // returns 6,7,8,9,10
$arr = Array(1,2,3,4,5,6,7,8,9,10);
for ($i=count($arr)-1; $i>=0; $i--)
unset($arr[$i]);
echo implode(',',$arr); // returns nothing
?>
$ids[$field_id] does not exist, you are using the value instead of the key.
You should simply unset using the right key :
if (in_array($field_id, $ids))
unset($ids[array_search($field_id, $ids)]);
If you want to remove the element from the array, shouldn't you 'splice' it out instead with array_splice?
From the PHP manual: http://php.net/manual/en/function.array-splice.php
Your foreach wont make any changes since a copy of array is used.. you will need to use pass by reference in order for this to work. one of the way is mentioned below
while(list($key,$value) = each($array)){
if(your reason to unset)
unset($array[$key]);
}
this will remove the element from the array.

php get 1st value of an array (associative or not)

This might sounds like a silly question. How do I get the 1st value of an array without knowing in advance if the array is associative or not?
In order to get the 1st element of an array I thought to do this:
function Get1stArrayValue($arr) { return current($arr); }
is it ok?
Could it create issues if array internal pointer was moved before function call?
Is there a better/smarter/fatser way to do it?
Thanks!
A better idea may be to use reset which "rewinds array's internal pointer to the first element and returns the value of the first array element"
Example:
function Get1stArrayValue($arr) { return reset($arr); }
As #therefromhere pointed out in the comment below, this solution is not ideal as it changes the state of the internal pointer. However, I don't think it is much of an issue as other functions such as array_pop also reset it.
The main concern that it couldn't be used when iterating over an array isn't an problem as foreach operates on a copy of the array. The PHP manual states:
Unless the array is referenced, foreach operates on a copy of the specified array and not the array itself.
This can be shown using some simple test code:
$arr = array("a", "b", "c", "d");
foreach ( $arr as $val ){
echo reset($arr) . " - " . $val . "\n";
}
Result:
a - a
a - b
a - c
a - d
To get the first element for any array, you need to reset the pointer first.
http://ca3.php.net/reset
function Get1stArrayValue($arr) {
return reset($arr);
}
If you don't mind losing the first element from the array, you can also use
array_shift() - shifts the first value of the array off and returns it, shortening the array by one element and moving everything down. All numerical array keys will be modified to start counting from zero while literal keys won't be touched.
Or you could wrap the array into an ArrayIterator and use seek:
$array = array("foo" => "apple", "banana", "cherry", "damson", "elderberry");
$iterator = new ArrayIterator($array);
$iterator->seek(0);
echo $iterator->current(); // apple
If this is not an option either, use one of the other suggestions.

Categories