Flattening array - php

I have a function which changes an array into the html list (ol/ul). The depth of the array is passed as an argument.
I wanted to do this in just a single function.
for($i = 0; $i < $depth; $i++) {
foreach($list_array as $li) {
if(! is_array($li))
{
$str .= '<li>' . $li . '</li>';
}
}
}
This code gives me the first dimension of the array. I'd like to flatten this array every time the $i increments.
Do you have any suggestions that could be helpful?
And yes, I'm aware of array_walk_recursive(), object iterators etc...I'd like to know if there's a simple way to do this task without using any ot those. I can't come up with anything.
And no, this is not any university project where I'm not allowed to use iterators etc.
EDIT:
print_list(array(
'some first element',
'some second element',
array(
'nested element',
'another nested element',
array(
'something else'
)
)
));
should output something like:
<ul>
<li>some first element</li>
<li>some second element</li>
<ul>
<li>nested element</li>
<li>another nested element</li> // etc

This is probably easiest to accomplish using recursion:
function print_list($array){
echo '<ul>';
// examine every value in the array
// (including values that may also be arrays)
for($array as $val){
if(is_array($val){
// when we discover the value is, in fact, an array
// print it as if it were the top-level array using
// this function
print_list($val);
}else{
// if this is a regular value, print it as a list item
echo '<li>'.$val.'</li>';
}
}
echo '</ul>';
}
If you want to do indentation, you could define a depth tracking parameter and a co-routine (print_list_internal($array, $depth)) or just add a default parameter (print_list($array,$depth=0)) and then print a number of spaces in front of anything depending on $depth.

function print_list($array) {
echo '<ul>';
// First print all top-level elements
foreach ($array as $val) {
if (!is_array($val)) {
echo '<li>'.$val.'</li>';
}
}
// Then recurse into all the sub-arrays
foreach ($array as $val) {
if (is_array($val)) {
print_list($val);
}
}
echo '</ul>';
}

Related

Resolve a multi dimensional array into fully specified endpoints

I need to turn each end-point in a multi-dimensional array (of any dimension) into a row containing the all the descendant nodes using PHP. In other words, I want to resolve each complete branch in the array. I am not sure how to state this more clearly, so maybe the best way is to give an example.
If I start with an array like:
$arr = array(
'A'=>array(
'a'=>array(
'i'=>1,
'j'=>2),
'b'=>3
),
'B'=>array(
'a'=>array(
'm'=>4,
'n'=>5),
'b'=>6
)
);
There are 6 end points, namely the numbers 1 to 6, in the array and I would like to generate the 6 rows as:
A,a,i,1
A,a,j,2
A,b,2
B,a,m,3
B,a,n,4
B,b,2
Each row contains full path of descendants to the end-point. As the array can have any number of dimensions, this suggested a recursive PHP function and I tried:
function array2Rows($arr, $str='', $out='') {
if (is_array($arr)) {
foreach ($arr as $att => $arr1) {
$str .= ((strlen($str)? ',': '')) . $att;
$out = array2Rows($arr1, $str, $out);
}
echo '<hr />';
} else {
$str .= ((strlen($str)? ',': '')) . $arr;
$out .= ((strlen($out)? '<br />': '')) . $str;
}
return $out;
}
The function was called as follows:
echo '<p>'.array2Rows($arr, '', '').'</p>';
The output from this function is:
A,a,i,1
A,a,i,j,2
A,a,b,3
A,B,a,m,4
A,B,a,m,n,5
A,B,a,b,6
Which apart from the first value is incorrect because values on some of the nodes are repeated. I have tried a number of variations of the recursive function and this is the closest I can get.
I will welcome any suggestions for how I can get a solution to this problem and apologize if the statement of the problem is not very clear.
You were so close with your function... I took your function and modified is slightly as follows:
function array2Rows($arr, $str='', $csv='') {
$tmp = $str;
if (is_array($arr)) {
foreach ($arr as $att => $arr1) {
$tmp = $str . ((strlen($str)? ', ': '')) . $att;
$csv = array2Rows($arr1, $tmp, $csv);
}
} else {
$tmp .= ((strlen($str)? ', ': '')) . $arr;
$csv .= ((strlen($csv)? '<br />': '')) . $tmp;
}
return $csv;
}
The only difference is the introduction of a temporary variable $tmp to ensure that you don't change the $str value before the recursion function is run each time.
The output from your function becomes:
This is a nice function, I can think of a few applications for it.
The reason that you are repeating the second to last value is that in your loop you you are appending the key before running the function on the next array. Something like this would work better:
function array2Rows($arr, &$out=[], $row = []) {
if (is_array($arr)) {
foreach ($arr as $key => $newArray) {
if (is_array($newArray)) {
$row[] = $key; //If the current value is an array, add its key to the current row
array2Rows($newArray, $out, $row); //process the new value
} else { //The current value is not an array
$out[] = implode(',',array_merge($row,[$key,$newArray])); //Add the current key and value to the row and write to the output
}
}
}
return $out;
}
This is lightly optimized and utilizes a reference to hold the full output. I've also changed this to use and return an array rather than strings. I find both of those changes to make the function more readable.
If you wanted this to return a string formatted similarly to the one that you have in your function, replace the last line with
return implode('<br>', $out);
Alternatively, you could do that when calling, which would be what I would call "best practice" for something like this; e.g.
$result = array2Rows($arr);
echo implode('<br>', $result);
Note, since this uses a reference for the output, this also works:
array2Rows($arr, $result);
echo implode('<br>', $result);

iterate through array, number of keys is variable, the first value being processed differently

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)

Multidimensional Arrays Nested to Unlimited Depth

I have a multidimensional array nested to an unknown/unlimited depth.
I'd like to be able to loop through every element.
I don't want to use, foreach(){foreach(){foreach(){}}} as I don't know the depth.
I'm eventually looking for all nested arrays called "xyz". Has anyone got any suggestions?
I'm eventually looking for all nested arrays called "xyz". Has anyone got any suggestions?
Sure. Building on the suggestions to use some iterators, you can do:
$iterator = new RecursiveIteratorIterator(
new RecursiveArrayIterator($array),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $key => $item) {
if (is_array($item) && $key === 'xyz') {
echo "Found xyz: ";
var_dump($item);
}
}
The important difference between the other answers and this being that the RecursiveIteratorIterator::SELF_FIRST flag is being employed to make the non-leaf (i.e. parent) items (i.e. arrays) visible when iterating.
You could also make use of a ParentIterator around the array iterator, rather than checking for arrays within the loop, to make the latter a little tidier.
Recursion.
Write a function that walks one array; for each element that is also an array, it calls itself; otherwise, when it finds the target string, it returns.
There is a vast difference between unknown and unlimited. However, you can make use of the SPL Iterators instead of using multiple nested foreach loops.
Example:
$array_obj = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($array_obj as $key => $value) {
echo $value;
}
Take a look to the RecursiveIteratorIterator interface.
$interface = new RecursiveIteratorIterator( new RecursiveArrayIterator($your_array) );
foreach($interface as $k=>$v) { /* your function*/ }
Using the comments above, I've found the answer:
function findXyz($array){
foreach($array as $foo=>$bar){
if (is_array($bar)){
if ($bar["xyz"]){
echo "<br />The array of xyz has now been found";
print_r($bar['xyz']);
}else{
findXyz($bar);
}
}
}
}
findXyz($myarray);
This loops through all nested arrays and looks for any element who has a sub-array of xyz, as per my original request. array_walk_array and RecursiveIteratorIterator were unable to achieve this.
Have you thought about using array_walk_recursive for this?
Another (slower) approach would be to flatten the array first before performing a search, ie:
$myarray = array('a','b',array(array(array('x'),'y','z')),array(array('p')));
function array_flatten($array,$return)
{
for($x = 0; $x <= count($array); $x++)
{
if(is_array($array[$x]))
{
$return = array_flatten($array[$x],$return);
}
else
{
if($array[$x])
{
$return[] = $array[$x];
}
}
}
return $return;
}
$res = array_flatten($myarray,array());
Or, for a recursive search, see here for an example:
function arrayRecursiveSearch($needle, $haystack, $path=""){
if(!is_array($haystack)){
die("second argument is not array");
}
global $matches;
foreach($haystack as $key=>$value)
{
if(preg_match("/$needle/i", $key)){
$matches[] = array($path . "$key/", "KEY: $key");
}
if(is_array($value)){
$path .= "$key/";
arrayRecursiveSearch($needle, $value, $path);
unset($path);
}else{
if(preg_match("/$needle/i", $value)){
$matches[] = array($path . "$key/", "VALUE: $value");
}
}
}
return $matches;
}
$arr = array("Asia"=>array('rambutan','duku'),
"Australia"=>array('pear','kiwi'),
"Arab"=>array('kurma'));
print_r(arrayRecursiveSearch("ra",$arr));

PHP - Grab the first element using a foreach

Wondering what would be a good method to get the first iteration on a foreach loop.
I want to do something different on the first iteration.
Is a conditional our best option on these cases?
Yes, if you are not able to go through the object in a different way (a normal for loop), just use a conditional in this case:
$first = true;
foreach ( $obj as $value )
{
if ( $first )
{
// do something
$first = false;
}
else
{
// do something
}
// do something
}
Even morer eleganterer:
foreach($array as $index => $value) {
if ($index == 0) {
echo $array[$index];
}
}
That example only works if you use PHP's built-in array append features/function or manually specify keys in proper numerical order.
Here's an approach that is not like the others listed here that should work via the natural order of any PHP array.
$first = array_shift($array);
//do stuff with $first
foreach($array as $elem) {
//do stuff with rest of array elements
}
array_unshift($array, $first); //return first element to top
You can simply add a counter to the start, like so:
$i = 0;
foreach($arr as $a){
if($i == 0) {
//do ze business
}
//the rest
$i++;
}
I saw this solution on a blog post in my search result set that brought up this post and I thought it was rather elegant. Though perhaps a bit heavy on processing.
foreach ($array as $element)
{
if ($element === reset($array))
echo 'FIRST ELEMENT!';
if ($element === end($array))
echo 'LAST ELEMENT!';
}
Do note there is also a warning on the post that this will only work if the array values are unique. If your last element is "world" and some random element in the middle is also "world" last element will execute twice.
hm
<?php
$i = 0;
foreach($ar as $sth) {
if($i++ == 0) {
// do something
}
// do something else
}
more elegant.
first = true
foreach(...)
if first
do stuff
first = false
This is also works
foreach($array as $element) {
if ($element === reset($array))
echo 'FIRST ELEMENT!';
if ($element === end($array))
echo 'LAST ELEMENT!';
}
foreach($array as $element) {
if ($element === reset($array))
echo 'FIRST ELEMENT!';
if ($element === end($array))
echo 'LAST ELEMENT!';
}
Here's an example that does not use foreach loop
<?php
// A sample indexed array
$cities = array("London", "Paris", "New York");
echo $cities[0]; // Outputs: London
// A sample associative array
$fruits = array("a" => "Apple", "b" => "Ball", "c" => "Cat");
echo array_values($fruits)["0"]; // Outputs: Apple
?>
What about using key() native php function? which should work fine with all kind of arrays (indexed, associative ) as it will always return the first key no matter if its inside or outside the loop.
$array = array(
'One' => 'value',
'Two' => 'value-2',
'Three' => 'value-3',
);
foreach ( $array as $index => $key ) {
if ( key( $array ) ) {
/**Do something with the first key*/
} else {
/**Do something else*/
}
}
if it indexed array, you have many options you can go through,
for me using counter is always fine and get the correct result all the time.
$array = array(
'One',
'Two',
'Three',
);
$i = 0;
foreach ( $array as $index => $key ) {
if ( $array[ $i ] ) {
/**Do something with the first key*/
} else {
/**Do something else*/
$i++;
}
}
Both should work fine with $key => $value loop and it will only return the first key.
Also I would like to add something, You can always archive your target in scripting with hundreds of different way, Its all about the way you want to use in this situation.

Find the last element of an array while using a foreach loop in PHP

I am writing a SQL query creator using some parameters. In Java, it's very easy to detect the last element of an array from inside the for loop by just checking the current array position with the array length.
for(int i=0; i< arr.length;i++){
boolean isLastElem = i== (arr.length -1) ? true : false;
}
In PHP they have non-integer indexes to access arrays. So you must iterate over an array using a foreach loop. This becomes problematic when you need to take some decision (in my case to append or/and parameter while building query).
I am sure there must be some standard way of doing this.
How do you solve this in PHP?
It sounds like you want something like this:
$numItems = count($arr);
$i = 0;
foreach($arr as $key=>$value) {
if(++$i === $numItems) {
echo "last index!";
}
}
That being said, you don't -have- to iterate over an "array" using foreach in php.
You could get the value of the last key of the array using end(array_keys($array)) and compare it to the current key:
$last_key = end(array_keys($array));
foreach ($array as $key => $value) {
if ($key == $last_key) {
// last element
} else {
// not last element
}
}
Note: This doesn't work because calling next() advances the array pointer, so you're skipping every other element in the loop
why so complicated?
foreach($input as $key => $value) {
$ret .= "$value";
if (next($input)==true) $ret .= ",";
}
This will add a , behind every value except the last one!
When toEnd reaches 0 it means it is at the last iteration of the loop.
$toEnd = count($arr);
foreach($arr as $key=>$value) {
if (0 === --$toEnd) {
echo "last index! $value";
}
}
The last value is still available after the loop, so if you just want to use it for more stuff after the loop this is better:
foreach($arr as $key=>$value) {
//something
}
echo "last index! $key => $value";
If you do not want to treat the last value as special inside loops. This should be faster if you have large arrays. (If you reuse the array after the loop inside the same scope you have to "copy" the array first).
//If you use this in a large global code without namespaces or functions then you can copy the array like this:
//$array = $originalArrayName; //uncomment to copy an array you may use after this loop
//end($array); $lastKey = key($array); //uncomment if you use the keys
$lastValue = array_pop($array);
//do something special with the last value here before you process all the others?
echo "Last is $lastValue", "\n";
foreach ($array as $key => $value) {
//do something with all values before the last value
echo "All except last value: $value", "\n";
}
//do something special with the last value here after you process all the others?
echo "Last is $lastValue", "\n";
And to answer your original question "in my case to append or/and parameter while building query"; this will loop over all the values, then join them together to a string with " and " between them but not before the first value or after the last value:
$params = [];
foreach ($array as $value) {
$params[] = doSomething($value);
}
$parameters = implode(" and ", $params);
There are already many answers, but it's worth to look into iterators as well, especially as it has been asked for a standard way:
$arr = range(1, 3);
$it = new CachingIterator(new ArrayIterator($arr));
foreach($it as $key => $value)
{
if (!$it->hasNext()) echo 'Last:';
echo $value, "\n";
}
You might find something that does work more flexible for other cases, too.
One way could be to detect if the iterator has next. If there is no next attached to the iterator it means you are in the last loop.
foreach ($some_array as $element) {
if(!next($some_array)) {
// This is the last $element
}
}
SINCE PHP 7.3 :
You could get the value of the last key of the array using array_key_last($array) and compare it to the current key:
$last_key = array_key_last($array);
foreach ($array as $key => $value) {
if ($key == $last_key) {
// last element
} else {
// not last element
}
}
to get first and last element from foreach array
foreach($array as $value) {
if ($value === reset($array)) {
echo 'FIRST ELEMENT!';
}
if ($value === end($array)) {
echo 'LAST ITEM!';
}
}
So, if your array has unique array values, then determining last iteration is trivial:
foreach($array as $element) {
if ($element === end($array))
echo 'LAST ELEMENT!';
}
As you see, this works if last element is appearing just once in array, otherwise you get a false alarm. In it is not, you have to compare the keys (which are unique for sure).
foreach($array as $key => $element) {
end($array);
if ($key === key($array))
echo 'LAST ELEMENT!';
}
Also note the strict coparision operator, which is quite important in this case.
Don't add a comma after the last value:
The array:
$data = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];
The function:
$result = "";
foreach($data as $value) {
$resut .= (next($data)) ? "$value, " : $value;
}
The result:
print $result;
lorem, ipsum, dolor, sit, amet
You can still use that method with associative arrays:
$keys = array_keys($array);
for ($i = 0, $l = count($array); $i < $l; ++$i) {
$key = $array[$i];
$value = $array[$key];
$isLastItem = ($i == ($l - 1));
// do stuff
}
// or this way...
$i = 0;
$l = count($array);
foreach ($array as $key => $value) {
$isLastItem = ($i == ($l - 1));
// do stuff
++$i;
}
Assuming you have the array stored in a variable...
foreach($array as $key=>$value)
{
echo $value;
if($key != count($array)-1) { echo ", "; }
}
If you need to do something for every element except either the first or the last and only if there is more than one element in the array, I prefer the following solution.
I know there are many solutions above and posted months/one year before mine, but this is something I feel is fairly elegant in its own right. The check every loop is also a boolean check as opposed to a numeric "i=(count-1)" check, which may allow for less overhead.
The structure of the loop may feel awkward, but you can compare it to the ordering of thead (beginning), tfoot (end), tbody (current) in HTML table tags.
$first = true;
foreach($array as $key => $value) {
if ($first) {
$first = false;
// Do what you want to do before the first element
echo "List of key, value pairs:\n";
} else {
// Do what you want to do at the end of every element
// except the last, assuming the list has more than one element
echo "\n";
}
// Do what you want to do for the current element
echo $key . ' => ' . $value;
}
For instance, in web development terms, if you want to add a border-bottom to every element except the last in an unordered list (ul), then you can instead add a border-top to every element except the first (the CSS :first-child, supported by IE7+ and Firefox/Webkit supports this logic, whereas :last-child is not supported by IE7).
You can feel free to reuse the $first variable for each and every nested loop as well and things will work just fine since every loop makes $first false during the first process of the first iteration (so breaks/exceptions won't cause issues).
$first = true;
foreach($array as $key => $subArray) {
if ($first) {
$string = "List of key => value array pairs:\n";
$first = false;
} else {
echo "\n";
}
$string .= $key . '=>(';
$first = true;
foreach($subArray as $key => $value) {
if ($first) {
$first = false;
} else {
$string .= ', ';
}
$string .= $key . '=>' . $value;
}
$string .= ')';
}
echo $string;
Example output:
List of key => value array pairs:
key1=>(v1_key1=>v1_val1, v1_key2=>v1_val2)
key2=>(v2_key1=>v2_val1, v2_key2=>v2_val2, v2_key3=>v2_val3)
key3=>(v3_key1=>v3_val1)
This should be the easy way to find the last element:
foreach ( $array as $key => $a ) {
if ( end( array_keys( $array ) ) == $key ) {
echo "Last element";
} else {
echo "Just another element";
}
}
Reference : Link
I have a strong feeling that at the root of this "XY problem" the OP wanted just implode() function.
As your intention of finding the EOF array is just for the glue. Get introduced to the below tactic. You need not require the EOF:
$given_array = array('column1'=>'value1',
'column2'=>'value2',
'column3'=>'value3');
$glue = '';
foreach($given_array as $column_name=>$value){
$where .= " $glue $column_name = $value"; //appending the glue
$glue = 'AND';
}
echo $where;
o/p:
column1 = value1 AND column2 = value2 AND column3 = value3
How about using "end"?
http://php.net/manual/en/function.end.php
It sounds like you want something like this:
$array = array(
'First',
'Second',
'Third',
'Last'
);
foreach($array as $key => $value)
{
if(end($array) === $value)
{
echo "last index!" . $value;
}
}
$array = array("dog", "rabbit", "horse", "rat", "cat");
foreach($array as $index => $animal) {
if ($index === array_key_first($array))
echo $animal; // output: dog
if ($index === array_key_last($array))
echo $animal; // output: cat
}
you can do a count().
for ($i=0;$i<count(arr);$i++){
$i == count(arr)-1 ? true : false;
}
or if you're looking for ONLY the last element, you can use end().
end(arr);
returns only the last element.
and, as it turns out, you CAN index php arrays by integers. It's perfectly happy with
arr[1];
You could also do something like this:
end( $elements );
$endKey = key($elements);
foreach ($elements as $key => $value)
{
if ($key == $endKey) // -- this is the last item
{
// do something
}
// more code
}
I kinda like the following as I feel it is fairly neat. Let's assume we're creating a string with separators between all the elements: e.g. a,b,c
$first = true;
foreach ( $items as $item ) {
$str = ($first)?$first=false:", ".$item;
}
Here's my solution:
Simply get the count of your array, minus 1 (since they start in 0).
$lastkey = count($array) - 1;
foreach($array as $k=>$a){
if($k==$lastkey){
/*do something*/
}
}
foreach ($array as $key => $value) {
$class = ( $key !== count( $array ) -1 ) ? " class='not-last'" : " class='last'";
echo "<div{$class}>";
echo "$value['the_title']";
echo "</div>";
}
Reference
If it is a single dimensional array you can do this to keep it short and sweet:
foreach($items as $idx => $item) {
if (!isset($items[$idx+1])) {
print "I am last";
}
}
Here's another way you could do it:
$arr = range(1, 10);
$end = end($arr);
reset($arr);
while( list($k, $v) = each($arr) )
{
if( $n == $end )
{
echo 'last!';
}
else
{
echo sprintf('%s ', $v);
}
}
If I understand you, then all you need is to reverse the array and get the last element by a pop command:
$rev_array = array_reverse($array);
echo array_pop($rev_array);
You could also try this to make your query... shown here with INSERT
<?php
$week=array('one'=>'monday','two'=>'tuesday','three'=>'wednesday','four'=>'thursday','five'=>'friday','six'=>'saturday','seven'=>'sunday');
$keys = array_keys($week);
$string = "INSERT INTO my_table ('";
$string .= implode("','", $keys);
$string .= "') VALUES ('";
$string .= implode("','", $week);
$string .= "');";
echo $string;
?>
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
Another way to go is to remember the previous loop cycle result and use that as the end result:
$result = $where = "";
foreach ($conditions as $col => $val) {
$result = $where .= $this->getAdapter()->quoteInto($col.' = ?', $val);
$where .= " AND ";
}
return $this->delete($result);

Categories