Currently, I am looping through 4 foreach loops:
foreach ($level0_array as $level0_key => $level1_array) {
//do something
foreach ($level1_array as $level1_key => $level2_array) {
//do something
foreach ($level2_array as $level2_key => $level3_array) {
//do something
foreach ($level3_array as $level3_key => $level4_value) {
//do something
}
}
}
}
Is it possible to do it if this loop is inside a function and it is supposed to get the number of levels to loop through dynamically? (Assuming in this case that $level0_array have enough levels in it)
i.e.
function ($level0_array, $number_of_levels) {
// loop. . .
}
Yes, there is, and it is called recursion:
function loopThroughLevels($level_array, $number_of_levels_left) {
foreach ($level_array as $level_key => $level_value) {
// do something
if (is_array($level_value) &&
($number_of_levels_left > 0)) {
loopThroughLevels($level_value, $number_of_levels_left - 1);
}
}
}
Here the function calls itself again, looping through a sub-level of the array as long as there is an array to loop through and there are levels left you want to loop through.
Related
As there is no iterator in PHP, the only way to loop through an array without getting the length of the array is to use foreach loop.
Let say I have the following loop:
foreach ($testing_array as $testing_entry) {
$result = my_testing_api_call($testing_entry);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
else {
sleep(5);
// I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
}
One way to do that is to use for loop instead.
for ( $i=0; $i < count($testing_array); $i++ ) {
$result = my_testing_api_call($testing_entry[$i]);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
else {
sleep(5);
$i--; //so it will repeat the current iteration.
}
}
The problem is that the $testing_array is not originally using number as index, so I have to do some data massage to use a for loop. Is there a way I can repeat a specific iteration in a foreach loop?
Perhaps a do-while will work for you.
Untested Code:
foreach ($testing_array as $testing_entry) {
do {
$result = my_testing_api_call($testing_entry);
if ($result == 'server dead') {
break 2; // break both loops
} elseif ($result == 'done') {
// do something to handle success code
} else {
sleep(5);
// I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
} while ($result !== 'done');
}
Or a single loop structure that destroys the input array as it iterates.
Untested Code:
$result = '';
while ($testing_array && $result !== 'server dead') {
$result = my_testing_api_call(current($testing_array));
if ($result == 'done') {
// do something to handle success code
array_shift($testing_array);
} elseif ($result !== 'server dead') {
sleep(5); // I want to retry my_testing_api_call with current $testing entry, but don't know what to write
}
}
Or you can use your for loop by indexing $test_array with array_values() if you don't need the keys in your // do something.
$testing_array = array_values($testing_array);
for ($i=0, $count=count($testing_array); $i < $count; ++$i) {
$result = my_testing_api_call($testing_entry[$i]);
if ($result == 'server dead') {
break;
} else if ($result == 'done') {
// do something to handle success code
} else {
sleep(5);
--$i; //so it will repeat the current iteration.
}
}
If you do need the keys down script, but you want to use for, you could store an indexed array of keys which would allow you to use $i to access the keys and maintain data synchronicity.
My final suggestion:
Use while (key($testing_array) !== null) {...} to move the pointer without destroying elements.
Code: (Demo)
$array1 = [
"one" => 1,
"two" => 2,
"three" => 3,
"four" => 4
];
while (key($array1)!==null) { // check if the internal pointer points beyond the end of the elements list or the array is empty
$current = current($array1);
$key = key($array1);
echo "$key => $current\n"; // display current key value pair
if ($current < 3) { // an arbitrary condition
echo "\t";
$array1[$key] = ++$current; // increment the current value
} else { // current value is satisfactory
echo "\t(advance pointer)\n";
next($array1); // move pointer
}
}
Output:
one => 1
one => 2
one => 3
(advance pointer)
two => 2
two => 3
(advance pointer)
three => 3
(advance pointer)
four => 4
(advance pointer)
You are trying to handle two different things in your loop, that makes it hard to write clean control flow. You could separate the retry-logic from the result handling:
function call_api_with_retry($entry) {
do {
if (is_defined($result)) sleep(5);
$result = my_testing_api_call($entry);
} while ($result != 'done' && $result != 'server dead');
return $result;
}
foreach ($testing_array as $testing_entry) {
$result = call_api_with_retry($testing_entry);
if ($result == 'server dead')
break;
else if ($result == 'done') {
// do something to handle success code
continue;
}
}
(there might be syntax errors, it's been a while since I wrote PHP code)
To repeat a single specific iteration you need to add a control mechanism, it is not an intended behavior after all.
There are many suggestions here, all of them are kinda over-engineered.
PHP is a high level derivate of C and both languages have the 'goto' operator, it has a bad reputation because people have historically used it too much.
In the end a foreach/while loop is internally nothing else than a 'goto' operation.
Here is the code:
foreach ($array as $key => $value)
{
restart:
if ($value === 12345) goto restart;
}
That's how this should be done just keep in mind that this can cause an endless loop, like in the example.
Look at the next complicated version without goto:
foreach ($array as $key => $value) while(1)
{
if ($value === 12345) continue;
break;
}
This is essentially the same as the goto, just with more code.
If you'd want to "break" or "continue" the foreach loop you'd write "break 2" or "continue 2"
Which is a more efficient way of comparing an array's keys with a template and filtering out empty values? Then operating on the remaining parts of the array?
$arr = array_intersect_key(array_filter($arr), $this->arrTemplate);
foreach ($arr as $_k => $_v) {
//Do something
Or this:
foreach ($arr as $_k => $_v) {
if (array_key_exists($_k, $this->arrTemplate)) {
if (empty($_v)) {
continue;
}
//Do something
}
}
Also as a side question, how can I measure efficiency for myself?
protected static function arrayShuffling($itemsArray)
{
$itemSwitching = array();Switching
$shItem= array();
foreach ($itemArray as $i => $myItem) {
if (!in_array($myItem->_id, $itemSwitching)) {
$itemSwitching[$i]['_id'] = $myItem->_id;
$itemSwitching[$i]['poistion'] = $myItem['details']['move_to_poistion'];
}
foreach ($itemSwitching as $t => $pinPrep) {
if ($event->_id == $pinPrep['_id']) {
$shuffeledItem[$itemSwitching[$t]['poistion']] = $myItem;
unset($itemArray[$i]);
}
}
}
foreach($shItem as $key=>$shffuledItem){
array_splice( $itemArray, $key, 0, array($shffuledItem));
}
}
I have this method that takes an array and then look at $myItem['details']['move_to_poistion'] index to see what position that element should be moved to. After that it takes that specific element out of the array and then uses splice to insert it back to the $myItem['details']['move_to_poistion'] position.
I am worried about too many foreach lops and want to know if some how we can shorten this.
thanks
Hi I have a PHP array with a variable number of keys (keys are 0,1,2,3,4.. etc)
I want to process the first value differently, and then the rest of the values the same.
What's the best way to do this?
$first = array_shift($array);
// do something with $first
foreach ($array as $key => $value) {
// do something with $key and $value
}
I would do this:
$firstDone = FALSE;
foreach ($array as $value) {
if (!$firstDone) {
// Process first value here
$firstDone = TRUE;
} else {
// Process other values here
}
}
...but whether that is the best way is debatable. I would use foreach over any other method, because then it does not matter what the keys are.
Here is one way:
$first = true;
foreach($array as $key => $value) {
if ($first) {
// something different
$first = false;
}
else {
// regular logic
}
}
$i = 0;
foreach($ur_array as $key => $val) {
if($i == 0) {
//first index
}
else {
//do something else
}
$i++;
}
I would do it like this if you're sure the array contains at least one entry:
processFirst($myArray[0]);
for ($i=1; $i<count($myArray); $1++)
{
processRest($myArray[$i]);
}
Otherwise you'll need to test this before processing the first element
I've made you a function!
function arrayCallback(&$array) {
$callbacks = func_get_args(); // get all arguments
array_shift($callbacks); // remove first element, we only want the callbacks
$callbackindex = 0;
foreach($array as $value) {
// call callback
$callbacks[$callbackindex]($value);
// make sure it keeps using last callback in case the array is bigger than the amount of callbacks
if(count($callbacks) > $callbackindex + 1) {
$callbackindex++;
}
}
}
If you call this function, it accepts an array and infinite callback arguments. When the array is bigger than the amount of supplied functions, it stays at the last function.
You can simply call it like this:
arrayCallback($array, function($value) {
print 'callback one: ' . $value;
}, function($value) {
print 'callback two: ' . $value;
});
EDIT
If you wish to avoid using a function like this, feel free to pick any of the other correct answers. It's just what you prefer really. If you're repeatedly are planning to loop through one or multiple arrays with different callbacks I suggest to use a function to re-use code. (I'm an optimisation freak)
How can I count the number of items with the same folder_id# ?
Here's my list of items:
item_id=1, folder_id=1
item_id=2, folder_id=1
item_id=3, folder_id=2
item_id=4, folder_id=3
Here's my UPDATED code:
foreach($items as $item)
{
if(????) //count of $item->folder_id > 1
{
//do something to $item->folder_id=1/$item->item_id=1
}
elseif(????) // cases where $item->item_id != $item->folder_id
{
//do something else to $item->folder_id=1/$item->item_id=2
}
else
{
//do something else to $item->folder_id=2/$item->item_id=3 and folder_id=3/item_id=4
}
}
I'm interested in code that can tell me that the count for folder_id=1 is 2, the count for folder_id=2 is 1, and the count for folder_id=3 is also 1.
UPDATE: I've changed the code above to now include an elseif() because it didn't quite ask all the things I was interested in. Besides counting the # of each folder_id, I'm also interested in distinguishing cases where folder_id != item_id. This would put item_id=1, item_id=2, item_id=3/4 in their own conditional clauses and wouldn't lump item_id=1 and item_id=2 as before.
Any assistance would be greatly appreciated, thank you,
If you had an array of folder_ids, you could use array_count_values to provide exactly the result you need. So let's make an array like that out of an array of objects using array_map:
$callback = function($item) { return $item->folder_id; };
$result = array_count_values(array_map($callback, $items));
This will end up with $result being
array(
1 => 2,
2 => 1,
3 => 1,
);
You can also write the callback inline for an one-liner, or if you are on PHP < 5.3 you can write it as a free function or alternatively using create_function.
See it in action (version for PHP < 5.3).
Update: follow up
Given $results from above, your loop would be:
foreach($results as $folder_id => $count) {
if($count > 1) {
// There were $count items with folder_id == $folder_id
}
// else blah blah
}
$totals = array();
foreach($items as $item) {
$totals[$item->folder_id]++;
}
function count_items(Array $items, $folder_id) {
return count(array_filter($items,
function($item) use($folder_id) {
return $item->folder_id === $folder_id;
}
));
}
I think this is what you are looking for.
function folder_count($items, $num)
{
$cnt = 0;
foreach($items as $item)
{
if($item->folder_id > $num)
{
$cnt++;
}
}
return $cnt;
}
foreach ($items as $item)
{
if (folder_count($items, 1))
{
//do something to $item->folder_id=1/$item->item_id=1, item_id=2
}
else
{
//do something else to $item->folder_id=2/$item->item_id=3 and folder_id=3/item_id=4
}
}