So I don't think I'm making full use of the foreach loop. Here is how I understand foreach.
It goes like foreach(arrayyouwanttoloopthrough as onevalueofthatarray)
No counter or incrementing required, it automatically pulls an array, value by value each loop, and for that loop it calls the value whatever is after the "as".
Stops once it's done with the array.
Should basically replace "for", as long as dealing with an array.
So something I try to do a lot with foreach is modify the array values in the looping array. But in the end I keep finding I have to use a for loop for that type of thing.
So lets say that I have an array (thing1, thing2, thing3, thing4) and I wanted to change it....lets say to all "BLAH", with a number at the end, I'd do
$counter = 0;
foreach($array as $changeval){
$counter++;
$changeval = "BLAH".$counter;
}
I would think that would change it because $changeval should be whatever value it's at for the array, right? But it doesn't. The only way I could find to do that in a] foreach is to set a counter (like above), and use the array with the index of counter. But to do that I'd have to set the counter outside the loop, and it's not even always reliable. For that I'd think it would be better to use a for loop instead of foreach.
So why would you use foreach over for? I think I'm missing something here because foreach has GOT to be able to change values...
Thanks
OH HEY One more thing. Are variables set in loops (like i, or key) accessible outside the loop? If I have 2 foreach loops like
foreach(thing as value)
would I have to make the second one
foreach(thing2 as value2) ]
or else it would have some problems?
You can use a reference variable instead:
foreach ($array as &$value)
{
$value = "foo";
}
Now the array is full of foo (note the & before $value).
Normally, the loop variable simply contains a copy of the corresponding array element, but the & (ampersand) tells PHP to make it a reference to the actual array element, rather than just a copy; hence you can modify it.
However, as #Tadeck says below, you should be careful in this case to destroy the reference after the loop has finished, since $value will still point to the final element in the array (so it's possible to accidentally modify it). Do this with unset:
unset($value);
The other option would be to use the $key => $value syntax:
foreach ($array as $key => $value)
{
$array[$key] = "foo";
}
To answer your second question: yes, they are subsequently accessible outside the loop, which is why it's good practice to use unset when using reference loop variables as in my first example. However, there's no need to use new variable names in subsequent loops, since the old ones will just be overwritten (with no unwanted consequences).
You want to pass by reference:
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value)
{
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
The extended foreach syntax is like this:
foreach ($array as $key => $value) {
}
Using the $key, you can index the original array:
foreach ($array as $key => $value) {
$array[$key] = "BLAH";
}
Incrementing from 0 to ...
foreach($array as $changeval){
if (!isset($counter)) { $counter = 0; }
$counter++;
$changeval = "BLAH".$counter;
}
Using the index/key of the ARRAY
foreach($array as $key => $changeval){
$changeval = "BLAH".$key;
}
You can use the key when looping with foreach:
foreach ($array as $key => $value)
{
$array[$key] = "foo";
}
But note that using a reference like Will suggested will be faster for such a case - but anyway, the $key => $value-syntax is quite useful sometimes.
Related
So $tr['tree'] is an array. $dic is an array stored as key values. I want to add the key source to that those arrays. It looks like the following code doesn't work as expected as I'm guessing $dic is a new instance of the array object inside $tr['tree'].
foreach($tr['tree'] as $dic){
$dic['source'] = $tr['source']." > ".$dic['name'];
}
Note, I'm coming from python where this would work brilliantly. So how would I do this in PHP?
foreach() creates copies of the items you're looping on, so $dic in the loop is detached from the array. If you want to modify the parent array, the safe method is to use:
foreach($array as $key => $value) {
$array[$key] = $new_value;
}
You could use a reference:
foreach($array as &$value) {
^---
$value = $new_value;
}
but that can lead to stupidly-hard-to-find bugs later. $value will REMAIN a reference after the foreach terminates. If you re-use that variable name later on for other stuff, you'll be modifying the array, because the var still points at it.
I'm testing the performances/hitches for References over copies of an array. I have the following code:
function ScoreWords($Value){
$WordList = array(
"Amazing" => 1,
"Value" => 300,
"Elements" => 30,
"Another" => 0
);
if (array_key_exists($Value,$WordList)){
return $WordList[$Value];
}
}
$array = ["Value","Another",1,2,3,4];
echo implode(',', $array), "<br>";
foreach ($array as &$value) {
ScoreWords($value);
}
echo implode(',', $array), "<br>";
foreach ($array as $value) {
ScoreWords($value);
}
echo implode(',', $array), "<br>";
But it seems, the code pasted above works semi-fine. Output is:
Value,Another,1,2,3,4
Value,Another,1,2,3,4
Value,Another,1,2,3,3
I found this by mistake as imploding was not actually necessary, but this sparks the question. Why is there a duplicated value for the final print rather than the correct value of 4? regardless of what the content of the array is. It seems to duplicate the second from last element as the last element?
What is happening is that after your 1st foreach, $value is a reference to the last element in the array. As that loop progressed it was a reference to each element, until finally stopping at the last one.
So, when the 2nd foreach runs, $value is still a reference. As that loop runs, it updates $value, which in turn, updates the last element in the array.
When it gets to the last element, it was set to 3 from the previous loop iteration. So, that's why it's set to 3 at the end.
To fix this, unset($value); after your first foreach.
The thing here is that you have to unset the value when you pass it by reference:
foreach ($array as &$value) {
ScoreWords($value);
}
unset($value); // break the reference with the last element
Warning Reference of a $value and the last array element remain even
after the foreach loop. It is recommended to destroy it by unset().
Foreach reference
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
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Strange behavior Of foreach
Just came across this bug recently in a PHP app. Not sure what's going on.
Basically, it shows up when using a combination of two foreach (one with &, one without).
Here's a test code that reproduce the problem:
$items = array(
array('id'=>1, 'name'=>'foo', 'value'=>150),
array('id'=>2, 'name'=>'bar', 'value'=>190)
);
foreach($items as &$item)
{
$item['percentage'] = $item['value'] * 0.75;
}
var_dump($items); // All Good
foreach($items as $item)
{
var_dump($item); // Shows 1st item twice
}
The second foreach loop runs the block twice, as expected, but $item remains stuck on the first item.
I understand this is likely caused by the use of the reference & in the first loop but I don't see why it should behave like this..
Any idea? is that a bug?
Getting the same result on 5.3.8, 5.3.10 & 5.4
Firstly, it is not a bug as Rasmus said. See https://bugs.php.net/bug.php?id=29992
In this, right implementation of modifying array with its loop variable with &.
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // break the reference with the last element
var_dump($arr); // All Good
foreach($arr as $value) {
var_dump($value); // All good
}
?>
This is odd PHP behavior that's been around pretty much forever, and it happens when you mix the use of a variable as reference then not reference like you did.
I deal with it with a naming convention as follows: When I am using foreach with a &$item, I name it like &$refItem. This keeps me from mixing types.
You need to unset the pointer after using foreach with a referenced array.
http://php.net/unset
This may be something that look more understand the problem of foreach
$last_value_of_first_foreach = 1;
$item = 2;
$c = 3;
$item = &$last_value_of_first_foreach ; // Think that this statement is first foreach loop
// Here $item is pointer to $last_value_of_first_foreach
// To Better understanding, let change the name ($reference_to_last_value = $item;)
now, the new loop is
$item = $c;
// Here, what it do is update value where the $item pointer refer to
// (mean $last_value_of_first_foreach )
// so, at here $last_value_of_first_foreach has value of $c
Now, back to your case, from the first foreach, the $item reference to the last element of array. now, when you assign something to $item in second foreach, what it do, is put something inside that one.
In the end of first loop
$item is pointer to $items[1]
The first of second loop
it will push the first element to the location where the $item point to (that mean $items[1], so that why $items[1] is replaced by $items[0].
In case you want to prevent this one, just unset the $item variable before next time usage.
This is a normal, not a weird behavior. Just read about reference here.
When you add & in front of a variable, you store the reference to a variable. So, when you re-use it, it will also change the contents of the referenced variable.
foreach($items as &$item) // You have $item here, prefixed with &.
{
$item['percentage'] = $item['value'] * 0.75;
}
var_dump($items);
foreach($items as $item) // And your re-use it here.
{
var_dump($item);
}
To solve this, add unset($item) in the 1st loop:
foreach($items as &$item)
{
$item['percentage'] = $item['value'] * 0.75;
unset($item); // add this, because you use &$item previously.
}
I want to do something like this:
foreach ($array as $key=>$value except when $key="id")
{
// whatever
}
... without having to put an "if" clause inside the body of the loop. It is not guaranteed that "id" will the be the first or last element in the array, and I don't really want to unset or slice the array, because that will be expensive, ugly, and not maintain the original data. I also definitely need to use both key and value inside the loop.
Any ideas?
I don't think that the if-clause is such a problem:
foreach ($array as $key => $value) {
if ($key == 'ignore_me') continue;
if ($key == 'ignore_me_2') continue;
If you want a fancy solution, you can use array_diff_key:
$loop_array = array_diff_key($actual_array, array('ignore_me' => NULL, 'ignore_me_2' => NULL));
foreach ($loop_array as $key => $value) {
#...
Go with the if-clause inside the loop.
There is nothing inelegant about it, it is the easiest to read and understand, and it is the most efficient.
If you have many keys to skip you could build a second hash to test against (in the if-clause):
foreach ($array as $key => $value) {
if (array_key_exists($key,$skip_keys)) continue;
...
}
I think you'll always end up to use an IF clause in the inner loop. Of all the options you already gave, that's the only one I would use for the speed and the memory consumption
AFAIK you can't do it without an if in the loop.
Like the keyword says, it's "for each", not "for most".
EDIT: like soulmerge says, you could do it with array_diff_key(), but if you're only missing out a single key it's more verbose and less memory efficient than just putting the if in the loop.
Another way of compacting the if statement is:
// ignore 1,5,8,9
foreach($arr as $key){
if(in_array($key, array(1,5,8,9)) continue;
...
}