If I iterate through an array twice, once by reference and then by value, PHP will overwrite the last value in the array if I use the same variable name for each loop. This is best illustrated through an example:
$array = range(1,5);
foreach($array as &$element)
{
$element *= 2;
}
print_r($array);
foreach($array as $element) { }
print_r($array);
Output:
Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 8 )
Note that I am not looking for a fix, I am looking to understand why this is happening. Also note that it does not happen if the variable names in each loop are not each called $element, so I'm guessing it has to do with $element still being in scope and a reference after the end of the first loop.
After the first loop $element is still a reference to the last element/value of $array.
You can see that when you use var_dump() instead of print_r()
array(5) {
[0]=>
int(2)
...
[4]=>
&int(2)
}
Note that & in &int(2).
With the second loop you assign values to $element. And since it's still a reference the value in the array is changed, too. Try it with
foreach($array as $element)
{
var_dump($array);
}
as the second loop and you'll see.
So it's more or less the same as
$array = range(1,5);
$element = &$array[4];
$element = $array[3];
// and $element = $array[4];
echo $array[4];
(only with loops and multiplication ...hey, I said "more or less" ;-))
Here's an explanation from the man himself:
$y = "some test";
foreach ($myarray as $y) {
print "$y\n";
}
Here $y is a symbol table entry referencing a string containing "some
test". On the first iteration you essentially do:
$y = $myarray[0]; // Not necessarily 0, just the 1st element
So now the storage associated with $y
is overwritten by the value from
$myarray. If $y is associated with
some other storage through a
reference, that storage will be
changed.
Now let's say you do this:
$myarray = array("Test");
$a = "A string";
$y = &$a;
foreach ($myarray as $y) {
print "$y\n";
}
Here $y is associated with the same
storage as $a through a reference so
when the first iteration does:
$y = $myarray[0];
The only place that "Test" string can
go is into the storage associated with
$y.
This is how you would fix this problem:
foreach($array as &$element)
{
$element *= 2;
}
unset($element); #gets rid of the reference and cleans the var for re-use.
foreach($array as $element) { }
Related
I'm trying to understand something about arrays in for each loops that might be obvious to many.
When i loop through my multi-dimensional array, i attempt to find sub arrays with no third element. If they have no third element, i want to add a third element to that sub array with a specific value.
$testArray = array (
array("Green", "Yellow", "Blue"),
array("Brown", "Silver"),
array("Orange", "Pink", "Black"),
);
When i use the foreach loop:
foreach ( $testArray as $key => $array ) {
if (count($array) == '2') {
$array[] = "None";
};
}
No errors are thrown but nothing happens. When i use the for each loop:
foreach ( $testArray as $key => $array ) {
if (count($array) == '2') {
$testArray[$key][] = "None";
};
}
It works as expected.
Sorry for the long preamble, my questions is:
Why aren't these two foreach loops doing the same thing? Thanks!
Because you need to access $testArray entries "by reference"
foreach ( $testArray as &$array ) {
if (count($array) == 2) {
$array[] = "None";
};
}
unset($array);
The problem here lies in the fact that foreach iterates over iterables and sets the iteration variable by value. This means that the $array which you are dealing with in the foreach is not the same value of the $testArray.
To rememdy this (and avoid introducing an $index variable to mutate an item in the array), you will need to tell foreach to pass the value by reference. References are PHP's answer to C-style pointers. If a variable references another, both variables point to the same value, so a modification to the contents of one is in effect a modification to the other. In your foreach, you can use &$array to have the loop pass you the items of $testArray by reference instead of by value:
foreach ( $testArray as $key => &$array ) {
if (count($array) == '2') {
$array[] = "None";
}
}
(Codepad Demo)
This aligns with PHP's references, where one variable can be made to reference another like so:
$a = array(1, 2, 3);
$b = &$a;
$b[] = 4;
print_r($a); // 1, 2, 3, 4
(Codepad Demo)
You experience a similar phenomenon with functions:
function byValue($a) {
$a[] = 4;
}
function byRef(&$a) {
$a[] = 5;
}
$a = array(1, 2, 3);
byValue($a);
print_r($a); // 1, 2, 3
byRef($a);
print_r($a); // 1, 2, 3, 5
(Codepad Demo)
The references section of the PHP docs has some examples about this syntax of foreach. Also note this (somewhat) related, but interesting read on foreach and references.
Also, on an unrelated note if you weren't aware: you don't need a semicolon after closing a block with } in PHP unless you're doing something like assigning a closure to a variable:
$someFunc = function($a) { return $a; }; //need one here
if(1 + 2 == 4) {
echo "I just broke math";
} // but you don't need one here
foreach loops do not pass the elements by reference. To get the first array to do what you want, it would have to be:
foreach ( $testArray as $key => &$array ) {
if (count($array) == '2') {
$array[] = "None";
};
}
This question already has answers here:
Reference Guide: What does this symbol mean in PHP? (PHP Syntax)
(24 answers)
Closed 9 years ago.
In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference from http://php.net/manual/en/control-structures.foreach.php.
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
echo $value;
}
$arr = array(1, 2, 3, 4);
foreach ($arr as $value) {
echo $value;
}
In both cases, it outputs 1234. What does adding & to $value actually do?
Any help is appreciated. Thanks!
It denotes that you pass $value by reference. If you change $value within the foreach loop, your array will be modified accordingly.
Without it, it'll passed by value, and whatever modification you do to $value will only apply within the foreach loop.
In the beginning when learning what passing by reference it isn't obvious....
Here's an example that I hope will hope you get a clearer understanding on what the difference on passing by value and passing by reference is...
<?php
$money = array(1, 2, 3, 4); //Example array
moneyMaker($money); //Execute function MoneyMaker and pass $money-array as REFERENCE
//Array $money is now 2,3,4,5 (BECAUSE $money is passed by reference).
eatMyMoney($money); //Execute function eatMyMoney and pass $money-array as a VALUE
//Array $money is NOT AFFECTED (BECAUSE $money is just SENT to the function eatMyMoeny and nothing is returned).
//So array $money is still 2,3,4,5
echo print_r($money,true); //Array ( [0] => 2 [1] => 3 [2] => 4 [3] => 5 )
//$item passed by VALUE
foreach($money as $item) {
$item = 4; //would just set the value 4 to the VARIABLE $item
}
echo print_r($money,true); //Array ( [0] => 2 [1] => 3 [2] => 4 [3] => 5 )
//$item passed by REFERENCE
foreach($money as &$item) {
$item = 4; //Would give $item (current element in array)value 4 (because item is passed by reference in the foreach-loop)
}
echo print_r($money,true); //Array ( [0] => 4 [1] => 4 [2] => 4 [3] => 4 )
function moneyMaker(&$money) {
//$money-array is passed to this function as a reference.
//Any changes to $money-array is affected even outside of this function
foreach ($money as $key=>$item) {
$money[$key]++; //Add each array element in money array with 1
}
}
function eatMyMoney($money) { //NOT passed by reference. ONLY the values of the array is SENT to this function
foreach ($money as $key=>$item) {
$money[$key]--; //Delete 1 from each element in array $money
}
//The $money-array INSIDE of this function returns 1,2,3,4
//Function isn't returing ANYTHING
}
?>
This is reference to a variable and the main use in foreach loop is that you can change the $value variable and that way the array itself would also change.
when you're just referencing values, you won't notice much difference in the case you've posted. the most simple example I can come up with describing the difference of referencing a variable by value versus by reference is this:
$a = 1;
$b = &$a;
$b++;
print $a; // 2
You'll notice $a is now 2 - because $b is a pointer to $a. if you didn't prefix the ampersand, $a would still be 1:
$a = 1;
$b = $a;
$b++;
print $a; // 1
HTH
Normally every function creates a copy of its parameters, works with them, and if you don't return them "deletes them" (when this happens depends on the language).
If run a function with &VARIABLE as parameter that means you added that variable by reference and in fact this function will be able to change that variable even without returning it.
Is there a native function that works like combine_subarrays below?
$foo = array(
array(1,2,3),
array(4,5,6),
array(7,8,9)
);
$n = 1; // desired element's position in each subarray
$bar = combine_subarrays($foo, $n);
// Result: $bar is array of all elements in 1st positions - [2,5,8]
Right now, I foreach through $foo and push the $nth element onto a new array that is then returned. If there's a native way to do it, it would be better.
A quick solution with global reference to $n would be:
$n = 1;
$bar = array_map(function($item) {
global $n;
return $item[$n];
},
$foo);
And the result is:
Array ( [0] => 2 [1] => 5 [2] => 8 )
There is no function that does exactly that but several ways to use the array functions instead of writing a loop. For example array_reduce*:
$bar = array_reduce($foo, function(&$result, $item) use ($n) {
$result[] = $item[$n]
});
**array_map is probably the better choice, it also preserves the original keys. See answer by #zeldi*
How can I get the current element number when I'm traversing a array?
I know about count(), but I was hoping there's a built-in function for getting the current field index too, without having to add a extra counter variable.
like this:
foreach($array as $key => value)
if(index($key) == count($array) ....
You should use the key() function.
key($array)
should return the current key.
If you need the position of the current key:
array_search($key, array_keys($array));
PHP arrays are both integer-indexed and string-indexed. You can even mix them:
array('red', 'green', 'white', 'color3'=>'blue', 3=>'yellow');
What do you want the index to be for the value 'blue'? Is it 3? But that's actually the index of the value 'yellow', so that would be an ambiguity.
Another solution for you is to coerce the array to an integer-indexed list of values.
foreach (array_values($array) as $i => $value) {
echo "$i: $value\n";
}
Output:
0: red
1: green
2: white
3: blue
4: yellow
foreach() {
$i++;
if(index($key) == $i){}
//
}
function Index($index) {
$Count = count($YOUR_ARRAY);
if ($index <= $Count) {
$Keys = array_keys($YOUR_ARRAY);
$Value = array_values($YOUR_ARRAY);
return $Keys[$index] . ' = ' . $Value[$index];
} else {
return "Out of the ring";
}
}
echo 'Index : ' . Index(0);
Replace the ( $YOUR_ARRAY )
I recently had to figure this out for myself and ended up on a solution inspired by #Zahymaka 's answer, but solving the 2x looping of the array.
What you can do is create an array with all your keys, in the order they exist, and then loop through that.
$keys=array_keys($items);
foreach($keys as $index=>$key){
echo "position: $index".PHP_EOL."item: ".PHP_EOL;
var_dump($items[$key]);
...
}
PS: I know this is very late to the party, but since I found myself searching for this, maybe this could be helpful to someone else
an array does not contain index when elements are associative. An array in php can contain mixed values like this:
$var = array("apple", "banana", "foo" => "grape", "carrot", "bar" => "donkey");
print_r($var);
Gives you:
Array
(
[0] => apple
[1] => banana
[foo] => grape
[2] => carrot
[bar] => donkey
)
What are you trying to achieve since you need the index value in an associative array?
There is no way to get a position which you really want.
For associative array, to determine last iteration you can use already mentioned counter variable, or determine last item's key first:
end($array);
$last = key($array);
foreach($array as $key => value)
if($key == $last) ....
I'd like to be able to extract some array elements, assign each of them to a variable and then unset these elements in the array.
Let's say I have
$myarray = array ( "one" => "eins", "two" => "zwei" , "three" => "drei") ;
I want a function suck("one",$myarray)as a result the same as if I did manually:
$one = "eins" ;
unset($myarray["one"]) ;
(I want to be able to use this function in a loop over another array that contains the names of the elements to be removed, $removethese = array("one","three") )
function suck($x, $arr) {
$x = $arr[$x] ;
unset($arr[$x]) ;
}
but this doesn't work. I think I have two prolbems -- how to say "$x" as the variable to be assigned to, and of function scope. In any case, if I do
suck("two",$myarray) ;
$two is not created and $myarray is unchanged.
Try this:
$myarray = array("one" => "eins", "two" => "zwei" , "three" => "drei");
suck('two', $myarray);
print_r($myarray);
echo $two;
function suck($x, &$arr) {
global $$x;
$$x = $arr[$x];
unset($arr[$x]);
}
Output:
Array
(
[one] => eins
[three] => drei
)
zwei
I'd build an new array with only the key => value pairs you want, and then toss it at extract().
You can do
function suck($x, $arr) {
$$x = $arr[$x] ;
unset($arr[$x]) ;
}
, using variable variables. This will only set the new variable inside the scope of "suck()".
You can also have a look at extract()
Why not this:
foreach ($myarray as $var => $val) {
$$var = $val;
unset($myarray[$var]);
echo "$var => ".$$var . "\n";
}
Output
one => eins
two => zwei
three => drei
If I've understood the question, you have two problems
The first is that you're setting the value of $x to be the value in the key-value pair. Then you're unsetting a key that doesn't exist. Finally, you're not returning anything. Here's what I mean:
Given the single element array $arr= array("one" => "eins") and your function suck() this is what happens:
First you call suck("one", $arr). The value of $x is then changed to "eins" in the line $x=$arr[$x]. Then you try to unset $x (which is invalid because you don't have an array entry with the key "eins"
You should do this:
function suck($x, $arr)
{
$tmp = $arr[$x];
unset($arr[$x]);
return $tmp
}
Then you can call this function to get the values (and remove the pair from the array) however you want. Example:
<?php
/* gets odd numbers in german from
$translateArray = array("one"=>"eins", "two"=>"zwei", "three"=>"drei");
$oddArray = array();
$oddArray[] = suck($translateArray,"one");
$oddArray[] = suck($translateArray, "three");
?>
The result of this is the array called translate array being an array with elements("eins","drei");
HTH
JB