PHP: unset does not work in foreach - php

I am trying to use unset in a foreach loop, but it is not working.
My code:
$aggr = $_GET;
foreach($aggr as $key => $value)
{
$pos_key = preg_replace('/dst_addr/', '', $key);
// why this not works:
unset($aggr[$key]);
unset($aggr[$key.'_h'.$pos_key]);
}
In the second iteration my key is eq $key.'_h'.$pos_key, but this key should be deleted and isn't.

PHP makes a copy of the array to iterate over it. Since you end up with an empty array anyway, use a stack:
$s = array_keys($_GET);
while($c = array_pop($s)) {
$pos_key = str_replace('dst_addr', '', $key);
$i = array_search($key . '_h' . $pos_key, $s);
if($i !== false) {
array_splice($s, $i, 1);
}
}

Related

How to convert $x['a']['b']['c'] = d to $x['a.b.c'] = d easily

I have a multiple level array field need to be submit via a form. Not sure what will be the easiest solution:
As php will auto convert dot to underscore, so i change it to array:
$form['a.b_b.c'] ==> <input name="a[b_b][c]" value="$form[a.b_b.c]">
And I got $_POST[a][b_b][c] correctly.
But what is the easiest way to assign this $_POST value back to the original $form['a.b_b.c'] without much loops? eg
$form['a.b_b.c'] = $_POST['a']['b_b']['c'];
Or is there a better way to walk around?
very close to this question but still not yet:
Convert dot syntax like "this.that.other" to multi-dimensional array in PHP
And here is my current solution:
foreach ($form as $k) {
$form[$k] = assign_val($_POST, $k);
}
function assign_val($arr = [], $key = '') {
$keys = explode('.', $key);
$val = &$arr;
foreach ($keys as $k) {
$val = &$val[$k];
}
return $val;
}
I would have a function flatten and do something like:
function flatten($array, $prefix = '') {
$result = array();
foreach($array as $key=>$value) {
if(is_array($value)) {
$result = $result + flatten($value, $prefix . $key . '.');
}
else {
$result[$prefix . $key] = $value;
}
}
return $result;
}
And then you can do:
$form = flatten($_POST);

Building multidimensional array dynamically

My intent is to take a series of strings, explode them by "_" and use the keys from that array to build a "master" array.
My rules are:
if key no exists -> create key under parent array
if key exists -> add key as new array to parent
if key is last -> add value
String Examples
total_players_count
total_rosters_count
players_season1_count
players_season2_count
rosters_season1_count
Expected Results
$main = array(
'total' => array(
'players' => array(
'count' => '123',
'rosters' => array(
'count' => '123')
)
),
'players' => array(
'season1' => array(
'count' => '123'
)
)
);
I hope this shows what I've expected to write. Now let me lead into how I disappointed myself through a series of failures last night ;)
The gist is, my recursive functions haven't been working so well so I'm not posting any of that code. I've been working with array_key_exists and that isn't getting me my expected results. I came close using prev(), current(), and end() but the issue stems from an unexpected number of keys I have to parse through (otherwise I would just loop 3 times and be done. I know the following would work if I had a limited amount of keys to parse; but I don't.
<?php
private function _get_section($parent_key, $sql) {
$data = array();
$data[$parent_key] = array();
foreach ($sql AS $key => $value) {
$keys = explode('_', $key);
if ($keys[0] == $parent_key) {
$i = 0;
$total_keys = count($keys);
for ($k = 1; $k < $total_keys; $k++) {
$i++;
if ($i == 1) {
echo '1. (' . $i . ') ' . $keys[$k];
$data[$parent_key][$keys[$k]] = array();
}
else if ($i > 1 && $i < $total_keys - 1) {
$data[$parent_key][$keys[$k - 1]][$keys[$k]] = array();
}
else if ($i == $total_keys - 1) {
$tmp = array_reverse($data);
// can't get the last key because I need to recursively loop
// through the results to find where to set this last
// key / value
}
}
$k = 0;
$i = 0;
}
$keys = array();
$total_keys = 0;
}
}
Solution (thank you)
$tree = array();
foreach ($sql AS $key => $value) {
$parts = explode('_', $key);
$val = $value;
foreach (array_reverse($parts) AS $part) {
$val = array($part => $val);
}
$tree = array_merge_recursive($tree, $val);
}
untested. hopefully i didnt need to read your code to fully understand what you want, because i didnt. i just looked at the expected results + the input, although i wonder what happened to rosters_season1_count?
$tree = array();
foreach ($strings as $string) {
$parts = explode('_', $string);
$path = '123';
foreach (array_reverse($parts) as $part) {
$path = array($part => $path);
}
$tree = array_merge_recursive($tree, $path);
}

Removing a value from an array php

Is there an easier way to do so?
$array = array(1,57,5,84,21,8,4,2,8,3,4);
$remove = 21;
$i = 0;
foreach ($array as $value ){
if( $value == $remove)
unset($array[$i])
$i++;
}
//array: 1,57,5,84,8,4,2,8,3,4
The array_search answer is good. You could also arraydiff like this
$array = array(1,57,5,84,21,8,4,2,8,3,4);
$remove = array(21);
$result = array_diff($array, $remove);
If you want to delete the first occurrence of the item in the array, use array_search to find the index of the item in the array rather than rolling your own loop.
$array = array(1,57,5,84,21,8,4,2,8,3,4);
$remove = 21;
$index = array_search($remove, $array);
if (index !== false)
unset($array[$index]);
To remove all duplicates, rerun the search/delete so long as it finds a match:
while (false !== ($index = array_search($remove, $array))) {
unset($array[$index]);
}
or find all keys for matching values and remove them:
foreach (array_keys($array, $remove) as $key) {
unset($array[$key]);
}
This is a little cleaner:
foreach($array as $key => $value) {
if ($value == $remove) {
unset($array[$key]);
break;
}
}
UPDATE
Alternatively, you can place the non-matching values into a temp array, then reset the original.
$temp = array();
foreach($array as $key => $value) {
if ($value != $remove) {
$temp[$key] = $value;
}
}
$array = $temp;

php array , delete a value if matching

I have an array like the following:
5-9-21,
5-10-22,
5-10-22,
5-11-23,
3-17-29,
3-19-31,
3-19-31,
1-25-31,
7-30-31
I wil get a value dynamically. Then I have to compare that value with the middle part of array.
9,
10,
10,
11,
17,
19,
19,
25,
30
If it's matching then I have to remove the whole part from array.
For example. If I am getting a value dynamically is 19, then I wil match with that array. And 3-19-31 is there two times. So it will remove all 3-19-31. After exploding with "-".
How can I do this?
foreach($array as $key=>$value){
$parts = explode('-', $value);
if($parts[1] == $search) {
unset($array[$key]);
}
}
Or if your search is an array
foreach($array as $key=>$value){
$parts = explode('-', $value);
if(in_array($parts[1], $search)) {
unset($array[$key]);
}
}
You could use array_filter to get a new array.
$new_arr = array_filter($old_arr, function($var) use ($input) {
$ret = explode('-', $var);
return !(isset($ret[1]) && $ret[1] === $input);
});
Or use a normal loop and then use unset to remove the values.
for ($arr as $key => $value) {
$ret = explode('-', $value);
if (isset($ret[1]) && $ret[1] === $input) {
unset($arr[$key]);
}
}
use this function, this will give you all the keys which are matched:
function custom_array_search($keyword,$array){
if(!is_array($array)){
return false;
}
$ret_keys = array();
foreach($array as $key=>$value){
if(strpos("-{$keyword}-",$value)!==false){
$ret_keys[] = $key;
}
}
return $ret_keys;
}
This function will give you all keys in an array.
Now you can delete those i.e. unset all keys from that array. :)
<?php
$arr = array('5-9-21', '5-10-22', '5-10-22', '5-11-23', '3-17-29', '3-19-31', '3-19-31', '1-25-31', '7-30-31');
$k = '10';
#print_r($arr);
foreach ($arr as $key => $value)
{
$t = explode('-',$value);
if($t[1] == $k)
{
unset($arr[$key]);
#echo "deleted<br>";
}
}
#print_r($arr);
?>
You can try this...
$arr; // your array
$value = 19;
foreach ($arr as $key=>$a)
{
if(strpos($a, "-".$value."-") !== false)
unset($arr[$key]);
}
There are few ways you can do this. If you are going to have only one digit always in the first eliment of your triplet, the following code should work;
$triplet_array = array(5-9-21, 5-10-22, 5-10-22, 5-11-23, 3-17-29, 3-19-31, 3-19-31, 1-25-31, 7-30-31);
$i = 0;
foreach($triplet_array as triplet){
$middle = substring($triplet,2,0);
if($middle == $my_dynamic_value) unset($triplet_array[$i]);
$i++
}
but, if the first part is not going to contain only one digit always;
foreach($triplet_array as triplet){
$this_triplet = explode('-',$triplet);
if($this_triplet[1] == $my_dynamic_value) unset($triplet_array[$i]);
$i++
}
hope this helps :-)

PHP How to determine the first and last iteration in a foreach loop?

The question is simple. I have a foreach loop in my code:
foreach($array as $element) {
//code
}
In this loop, I want to react differently when we are in first or last iteration.
How to do this?
If you prefer a solution that does not require the initialization of the counter outside the loop, then you can compare the current iteration key against the function that tells you the last / first key of the array.
PHP 7.3 and newer:
foreach ($array as $key => $element) {
if ($key === array_key_first($array)) {
echo 'FIRST ELEMENT!';
}
if ($key === array_key_last($array)) {
echo 'LAST ELEMENT!';
}
}
PHP 7.2 and older:
PHP 7.2 is already EOL (end of life), so this is here just for historic reference. Avoid using.
foreach ($array as $key => $element) {
reset($array);
if ($key === key($array)) {
echo 'FIRST ELEMENT!';
}
end($array);
if ($key === key($array)) {
echo 'LAST ELEMENT!';
}
}
You could use a counter:
$i = 0;
$len = count($array);
foreach ($array as $item) {
if ($i == 0) {
// first
} else if ($i == $len - 1) {
// last
}
// …
$i++;
}
To find the last item, I find this piece of code works every time:
foreach( $items as $item ) {
if( !next( $items ) ) {
echo 'Last Item';
}
}
A more simplified version of the above and presuming you're not using custom indexes...
$len = count($array);
foreach ($array as $index => $item) {
if ($index == 0) {
// first
} else if ($index == $len - 1) {
// last
}
}
Version 2 - Because I have come to loathe using the else unless necessary.
$len = count($array);
foreach ($array as $index => $item) {
if ($index == 0) {
// first
// do something
continue;
}
if ($index == $len - 1) {
// last
// do something
continue;
}
}
You could remove the first and last elements off the array and process them separately.
Like this:
<?php
$array = something();
$first = array_shift($array);
$last = array_pop($array);
// do something with $first
foreach ($array as $item) {
// do something with $item
}
// do something with $last
?>
Removing all the formatting to CSS instead of inline tags would improve your code and speed up load time.
You could also avoid mixing HTML with php logic whenever possible.
Your page could be made a lot more readable and maintainable by separating things like this:
<?php
function create_menu($params) {
//retrieve menu items
//get collection
$collection = get('xxcollection') ;
foreach($collection as $c) show_collection($c);
}
function show_subcat($val) {
?>
<div class="sub_node" style="display:none">
<img src="../images/dtree/join.gif" align="absmiddle" style="padding-left:2px;" />
<a id="'.$val['xsubcatid'].'" href="javascript:void(0)" onclick="getProduct(this , event)" class="sub_node_links" >
<?php echo $val['xsubcatname']; ?>
</a>
</div>
<?php
}
function show_cat($item) {
?>
<div class="node" >
<img src="../images/dtree/plus.gif" align="absmiddle" class="node_item" id="plus" />
<img src="../images/dtree/folder.gif" align="absmiddle" id="folder">
<?php echo $item['xcatname']; ?>
<?php
$subcat = get_where('xxsubcategory' , array('xcatid'=>$item['xcatid'])) ;
foreach($subcat as $val) show_subcat($val);
?>
</div>
<?php
}
function show_collection($c) {
?>
<div class="parent" style="direction:rtl">
<img src="../images/dtree/minus.gif" align="absmiddle" class="parent_item" id="minus" />
<img src="../images/dtree/base.gif" align="absmiddle" id="base">
<?php echo $c['xcollectionname']; ?>
<?php
//get categories
$cat = get_where('xxcategory' , array('xcollectionid'=>$c['xcollectionid']));
foreach($cat as $item) show_cat($item);
?>
</div>
<?php
}
?>
An attempt to find the first would be:
$first = true;
foreach ( $obj as $value )
{
if ( $first )
{
// do something
$first = false; //in order not to get into the if statement for the next loops
}
else
{
// do something else for all loops except the first
}
}
Simply this works!
// Set the array pointer to the last key
end($array);
// Store the last key
$lastkey = key($array);
foreach($array as $key => $element) {
....do array stuff
if ($lastkey === key($array))
echo 'THE LAST ELEMENT! '.$array[$lastkey];
}
Thank you #billynoah for your sorting out the end issue.
1: Why not use a simple for statement? Assuming you're using a real array and not an Iterator you could easily check whether the counter variable is 0 or one less than the whole number of elements. In my opinion this is the most clean and understandable solution...
$array = array( ... );
$count = count( $array );
for ( $i = 0; $i < $count; $i++ )
{
$current = $array[ $i ];
if ( $i == 0 )
{
// process first element
}
if ( $i == $count - 1 )
{
// process last element
}
}
2: You should consider using Nested Sets to store your tree structure. Additionally you can improve the whole thing by using recursive functions.
Best answer:
$arr = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
foreach ($arr as $a) {
// This is the line that does the checking
if (!each($arr)) echo "End!\n";
echo $a."\n";
}
The most efficient answer from #morg, unlike foreach, only works for proper arrays, not hash map objects. This answer avoids the overhead of a conditional statement for every iteration of the loop, as in most of these answers (including the accepted answer) by specifically handling the first and last element, and looping over the middle elements.
The array_keys function can be used to make the efficient answer work like foreach:
$keys = array_keys($arr);
$numItems = count($keys);
$i=0;
$firstItem=$arr[$keys[0]];
# Special handling of the first item goes here
$i++;
while($i<$numItems-1){
$item=$arr[$keys[$i]];
# Handling of regular items
$i++;
}
$lastItem=$arr[$keys[$i]];
# Special handling of the last item goes here
$i++;
I haven't done benchmarking on this, but no logic has been added to the loop, which is were the biggest hit to performance happens, so I'd suspect that the benchmarks provided with the efficient answer are pretty close.
If you wanted to functionalize this kind of thing, I've taken a swing at such an iterateList function here. Although, you might want to benchmark the gist code if you're super concerned about efficiency. I'm not sure how much overhead all the function invocation introduces.
For SQL query generating scripts, or anything that does a different action for the first or last elements, it is much faster (almost twice as fast) to avoid using unneccessary variable checks.
The current accepted solution uses a loop and a check within the loop that will be made every_single_iteration, the correct (fast) way to do this is the following :
$numItems = count($arr);
$i=0;
$firstitem=$arr[0];
$i++;
while($i<$numItems-1){
$some_item=$arr[$i];
$i++;
}
$last_item=$arr[$i];
$i++;
A little homemade benchmark showed the following:
test1: 100000 runs of model morg
time: 1869.3430423737 milliseconds
test2: 100000 runs of model if last
time: 3235.6359958649 milliseconds
And it's thus quite clear that the check costs a lot, and of course it gets even worse the more variable checks you add ;)
With Keys and Values this works as well:
foreach ($array as $key => $value) {
if ($value === end($array)) {
echo "LAST ELEMENT!";
}
}
Using a Boolean variable is still the most reliable, even if you want to check the first appearance of a $value (I found it more useful in my situation and in many situations), such like this:
$is_first = true;
foreach( $array as $value ) {
switch ( $value ) {
case 'match':
echo 'appeared';
if ( $is_first ) {
echo 'first appearance';
$is_first = false;
}
break;
}
}
if( !next( $array ) ) {
echo 'last value';
}
}
Then how about !next( $array ) to find the last $value which will return true if there's no next() value to iterate.
And I prefer to use a for loop instead of foreach if I were going to use a counter, like this:
$len = count( $array );
for ( $i = 0; $i < $len; $i++ ) {
$value = $array[$i];
if ($i === 0) {
// first
} elseif ( $i === $len - 1 ) {
// last
}
// …
$i++;
}
I came across this thread when I have the same problem. I only need to get the first element then I re-analyze my code until this came up to my mind.
$firstElement = true;
foreach ($reportData->result() as $row)
{
if($firstElement) { echo "first element"; $firstElement=false; }
// Other lines of codes here
}
The above codes are great and complete but if you only need just the first element then you may try this code.
Using reset($array) and end($array)
<?php
$arrays = [1,2,3,4,5];
$first = reset($arrays);
$last = end($arrays);
foreach( $arrays as $array )
{
if ( $first == $array )
{
echo "<li>{$array} first</li>";
}
else if ( $last == $array )
{
echo "<li>{$array} last</li>";
}
else
{
echo "<li>{$array}</li>";
}
}
Demo repl.it
Not sure if it still necessary. But the following solution should work with iterators and does not require count.
<?php
foreach_first_last(array(), function ($key, $value, $step, $first, $last) {
echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});
foreach_first_last(array('aa'), function ($key, $value, $step, $first, $last) {
echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});
echo PHP_EOL;
foreach_first_last(array('aa', 'bb', 'cc'), function ($key, $value, $step, $first, $last) {
echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});
echo PHP_EOL;
function foreach_first_last($array, $cb)
{
$next = false;
$current = false;
reset($array);
for ($step = 0; true; ++$step) {
$current = $next;
$next = each($array);
$last = ($next === false || $next === null);
if ($step > 0) {
$first = $step == 1;
list ($key, $value) = $current;
if (call_user_func($cb, $key, $value, $step, $first, $last) === false) {
break;
}
}
if ($last) {
break;
}
}
}
You can use an anonymous function, too:
$indexOfLastElement = count($array) - 1;
array_walk($array, function($element, $index) use ($indexOfLastElement) {
// do something
if (0 === $index) {
// first element‘s treatment
}
if ($indexOfLastElement === $index) {
// last not least
}
});
Three more things should be mentioned:
If your array isn‘t indexed strictly (numerically) you must pipe your array through array_values first.
If you need to modify the $element you have to pass it by reference (&$element).
Any variables from outside the anonymous function you need inside, you‘ll have to list them next to $indexOfLastElement inside the use construct, again by reference if needed.
You can use the counter and array length.
$array = array(1,2,3,4);
$i = 0;
$len = count($array);
foreach ($array as $item) {
if ($i === 0) {
// first
} else if ($i === $len - 1) {
// last
}
// …
$i++;
}
foreach ($arquivos as $key => $item) {
reset($arquivos);
// FIRST AHEAD
if ($key === key($arquivos) || $key !== end(array_keys($arquivos)))
$pdf->cat(null, null, $key);
// LAST
if ($key === end(array_keys($arquivos))) {
$pdf->cat(null, null, $key)
->execute();
}
}
The simplest way would be
$array = [9,5,6,4,7,8];
$current_iteration = 0;
foreach($array as $item){
if( 0 === $current_iteration ){
echo 'this is the first item: ' . $item;
}
if( (count($array) - 1) === $current_iteration){
echo 'this is the last item: ' . $item;
}
$current_iteration++;
}
Try this:
function children( &$parents, $parent, $selected ){
if ($parents[$parent]){
$list = '<ul>';
$counter = count($parents[$parent]);
$class = array('first');
foreach ($parents[$parent] as $child){
if ($child['id'] == $selected) $class[] = 'active';
if (!--$counter) $class[] = 'last';
$list .= '<li class="' . implode(' ', $class) . '"><div>' . $child['name'] . '</div></li>';
$class = array();
$list .= children($parents, $child['id'], $selected);
}
$list .= '</ul>';
return $list;
}
}
$output .= children( $parents, 0, $p_industry_id);

Categories