While working on a project which checks the if Laravel models are related to each other I noticed some (weird?) pointer behavior going on with PHP. Below is a minimal example to reproduce what I found.
<?php
$arr = ['a', 'b', ['c']];
foreach($arr as &$letter) {
if (!is_array($letter)) {
$letter = [$letter];
}
}
dump($arr);
foreach($arr as $letter) {
dump($arr);
}
function dump(...$dump) {
echo '<pre>';
var_dump($dump);
echo '</pre>';
}
At first I expected the dumps in this response to all return the same data:
[ ['a'], ['b'], ['c'] ]
But that is not what happened, I got the following responses:
[ ['a'], ['b'], ['c'] ]
[ ['a'], ['b'], ['a'] ]
[ ['a'], ['b'], ['b'] ]
[ ['a'], ['b'], ['b'] ]
A running example can be found here.
Why do the pointers act this way? How can I update $letter in the first loop without having to do $arr[$key] = $letter?
Edit: As people seem to be misunderstanding why there is a second foreach loop, this is to show that the array is changing without being reassigned
According to the PHP documentation:
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
// Without an `unset($value)`, `$value` is still a reference to the last item: `$arr[3]`
foreach ($arr as $key => $value) {
// $arr[3] will be updated with each value from $arr...
echo "{$key} => {$value} ";
print_r($arr);
}
// ...until ultimately the second-to-last value is copied onto the last value
/* output:
0 => 2 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 2 )
1 => 4 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 4 )
2 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 )
3 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 ) */
First of all: PHP doesn't have pointers, it has references. See What references are and What references are not for more information.
The reason this happens is that $letter after the foreach loop still holds a reference to the last element of your array (which is [c]). So in your second loop, you're overriding not only $letter while looping but also the reference it points to.
To solve the problem you need to unset($letter) after your first loop:
$arr = ['a', 'b', ['c']];
foreach($arr as &$letter) {
if (!is_array($letter)) {
$letter = [$letter];
}
}
unset($letter); // this is important
dump($arr);
foreach($arr as $letter) {
dump($arr);
}
function dump(...$dump) {
echo '<pre>';
var_dump($dump);
echo '</pre>';
}
Related
I'm trying to convert mib-style strings into PHP array indices. The trick is that I have to do this for a variable number of strings. As an example:
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
# Given the above, generate the below:
$array = [ 1 => [ 1 => [1 => 1, 2 => 2] ], 2 => [1 => 1] ] ] ] ]
I can't think of a way to do it that isn't just a brute-force, inefficient method. Any helpful function/approach/advice is welcome.
You could take a recursive approach since the problem/result you provide seems to be of a recursive nature. (You can achieve the same result with a loop, applying the same logic as the recursive function ofcourse)
So under the assumption that there are no conflicting string inputs/edge cases what so ever, the following could be one approach:
Loop over all the strings and their values, break it up and create a nested structure by passing the result array by its reference.
function createNested($pieces, $currentIndex, &$previous, $value)
{
$index = $pieces[$currentIndex];
// Our base case: when we reached the final/deepest level of nesting.
// Hence when the we reached the final index.
if ($currentIndex == count($pieces) - 1) {
// Can now safely assign the value to index.
$previous[$index] = $value;
} else {
// Have to make sure we do not override the key/index.
if (!key_exists($index, $previous)) {
$previous[$index] = [];
}
// If the key already existed we can just make a new recursive call (note one level deeper as we pass the array that $previous[$index] points to.
createNested($pieces, $currentIndex + 1, $previous[$index], $value);
}
}
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
$result = [];
foreach ($strings as $string => $value) {
// Break up the string by .
createNested(explode('.', $string), 0, $result, $value);
}
echo '<pre>';
print_r($result);
echo '</pre>';
Will output:
Array
(
[1] => Array
(
[1] => Array
(
[1] => 1
[2] => 2
)
[2] => Array
(
[1] => 1
)
)
)
How can I reference the following dynamic arrays' elements ?
$log = array();
$arr1 = array ('a'=>'6:16pm','b'=>2,'c'=>3,'d'=>4,'e'=>5);
$arr2 = array ('a'=>'6:24pm','b'=>20,'c'=>30,'d'=>40,'e'=>50);
$log = array_merge($log, array($arr1['a']=>$arr1));
$log = array_merge($log, array($arr2['a']=>$arr2)); //<-- to use time as key
print_r($log);
for ($x = 0; $x < count($log); $x++) {
print_r ($log[0][$x]['a']); // <-- referencing issue Undefined offset: 0 .. line 20
}
//------ produces
Array
(
[6:16pm] => Array
(
[a] => 6:16pm
[b] => 2
[c] => 3
[d] => 4
[e] => 5
)
[6:24pm] => Array
(
[a] => 6:24pm
[b] => 20
[c] => 30
[d] => 40
[e] => 50
)
)
I'm pretty sure it has something to do with the way I'm naming the $log main array.. and there's probably a better way to do what I wish(.. add/ new elements to $log using the time key) - Still a php noob unfortunately. Thanks for any pointers.
It's a little unclear, but just create an array from the two and use the a index:
$log = array($arr1, $arr2);
foreach($log as $values) {
echo $values['a']; // 6:16pm
}
Or if you want the time as the index, then re-index on a:
$log = array($arr1, $arr2);
$log = array_column($log, null, 'a');
foreach($log as $time => $values) {
echo $time; // 6:16pm
echo $values['b']; // 2
}
It's prettier, but there is no need for the time as the index unless you are going to use ksort or access by index:
echo $log['6:16pm']['b'];
in cases that you don't know your key is recommended that you use foreach statement:
$logs = array();
$arr1 = array ('a'=>'6:16pm','b'=>2,'c'=>3,'d'=>4,'e'=>5);
$arr2 = array ('a'=>'6:24pm','b'=>20,'c'=>30,'d'=>40,'e'=>50);
$logs = array_merge($logs, array($arr1['a']=>$arr1));
$logs = array_merge($logs, array($arr2['a']=>$arr2)); //<-- to use time as key
$logs = array();
$arr1 = array('a' => '6:16pm', 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5);
$arr2 = array('a' => '6:24pm', 'b' => 20, 'c' => 30, 'd' => 40, 'e' => 50);
$logs = array_merge($logs, array($arr1['a'] => $arr1));
$logs = array_merge($logs, array($arr2['a'] => $arr2)); //<-- to use time as key
foreach ($logs as $time => $log) {
//index:
print_r($time);
//array:
print_r($log);
// a array key:
print_r($log['a']);
//go through all keys:
foreach ($log as $letter => $value) {
//index:
print_r($letter);
//value: a
print_r($value);
}
}
You can not call array like
print_r ($log[0]);
Because your array has key. which is 6:16pm for first one and 6:24pm for second. you have to call it by key name you have assigned. your array should be called like this in everywhere even in loop
print_r ($log["6:16pm"]);
Basically, I am fetching data from an API and that API has duplicate numbers. I want to count the duplicate numbers within the foreach, but its not working for some reason. I am not sure what I am doing wrong. You can see there is 33 many times. See the picture how it prints. It should be showing something like this
Array
(
[33] => 5
[2] => 3
)
Code:
foreach ($parsed_json1->numbers as $item) {
$ui = $item->no;
$currentp= number_format($ui, 6);
//echo $currentp;
//echo "</br>";
$array = array($currentp);
$vals = array_count_values($array);
//echo 'No. of NON Duplicate Items: '.count($vals).'<br><br>';
print_r($vals);
}
The fundamental flaw of your code is that you take one value and format it, then you put it into array and then do array_count_values on it.
See the difference:
$arr = [33, 32, 23, 33, 22, 23, 32, 33, 33];
print_r(array_count_values($arr));
foreach($arr as $a) {
$currentp= number_format($a, 6);
$array = array($currentp);
$vals = array_count_values($array);
print_r($vals);
}
First print_r prints:
Array
(
[33] => 4
[32] => 2
[23] => 2
[22] => 1
)
And the print_r inside foreach prints:
Array([33.000000] => 1)
Array([32.000000] => 1)
Array([23.000000] => 1)
Array([33.000000] => 1)
Array([22.000000] => 1)
Array([23.000000] => 1)
Array([32.000000] => 1)
Array([33.000000] => 1)
Array([33.000000] => 1)
Do you realize the difference?
I guess, what you need is
$arr = (array_count_values([33, 32, 23, 33, 22, 23, 32, 33, 33]));
$new_arr = [];
foreach($arr as $k => $a) {
$new_arr[number_format($k, 6)] = $a;
}
print_r($new_arr);
Which outputs:
Array
(
[33.000000] => 4
[32.000000] => 2
[23.000000] => 2
[22.000000] => 1
)
Not sure why you use number_format(), but I'm guessing you need something like this
$num_array = []; // Initialize number array
foreach ($parsed_json1->numbers as $item) {
$ui = $item->no;
$currentp= number_format($ui, 6);
$num_array[] = $currentp; // Store each number first to number array
}
// After foreach loop, do array_count_values to number array
$vals = array_count_values($num_array);
print_r($vals); //Print result
All I am trying to do is flatten an arbitrary array of integers.
Here is my code:
<?php
$list_of_lists_of_lists = [[1, 2, [3]], [4, 3, 4, [5, 3, 4]], 3];
$flattened_list = [];
function flatten($l){
foreach ($l as $value) {
if (is_array($value)) {
flatten($value);
}else{
$flattened_list[] = $value;
}
}
}
flatten($list_of_lists_of_lists);
print_r($flattened_list);
?>
When I run this code, I get this:
Array ( )
I have no idea why. I did the exact same code in Python and it worked fine.
Can you guys point out, where I went wrong?
First you have a scope issue, that your result array is out of scope in the function. So just pass it as argument from call to call.
Second you also don't return your result array, which you have to do, if you want to use the result outside of the function.
Corrected code:
$list_of_lists_of_lists = [[1, 2, [3]], [4, 3, 4, [5, 3, 4]], 3];
function flatten($l, $flattened_list = []){
foreach ($l as $value) {
if(is_array($value)) {
$flattened_list = flatten($value, $flattened_list);
} else {
$flattened_list[] = $value;
}
}
return $flattened_list;
}
$flattened_list = flatten($list_of_lists_of_lists);
print_r($flattened_list);
output:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 3
[5] => 4
[6] => 5
[7] => 3
[8] => 4
[9] => 3
)
I need to recursively reverse a HUGE array that has many levels of sub arrays, and I need to preserve all of the keys (which some are int keys, and some are string keys), can someone please help me? Perhaps an example using array_reverse somehow? Also, is using array_reverse the only/best method of doing this?
Thanks :)
Try this:
function array_reverse_recursive($arr) {
foreach ($arr as $key => $val) {
if (is_array($val))
$arr[$key] = array_reverse_recursive($val);
}
return array_reverse($arr);
}
Recursively:
<?php
$a = array(1,3,5,7,9);
print_r($a);
function rev($a) {
if (count($a) == 1)
return $a;
return array_merge(rev(array_slice($a, 1, count($a) - 1)), array_slice($a, 0, 1));
}
$a = rev($a);
print_r($a);
?>
output:
Array
(
[0] => 1
[1] => 3
[2] => 5
[3] => 7
[4] => 9
)
Array
(
[0] => 9
[1] => 7
[2] => 5
[3] => 3
[4] => 1
)
Reversing a HUGE php array in situ (but not recursively):
function arrayReverse(&$arr){
if (!is_array($arr) || empty($arr)) {
return;
}
$rev = array();
while ( false !== ( $val = end($arr) ) ){
$rev[ key($arr) ] = $val;
unset( $arr[ key($arr) ] );
}
$arr = $rev;
}
//usage
$test = array(5, 'c'=>100, 10, 15, 20);
arrayReverse($test);
var_export($test);
// result: array ( 3 => 20, 2 => 15, 1 => 10, 'c' => 100, 0 => 5, )