PHP break Nested Multi-dimensional array into single multi-dimensional array - php

I want to break the below nested array in simple associative array.
Input
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
[children] => Array
(
[0] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
[children] => Array
(
)
)
[1] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
[children] => Array
(
[0] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
[children] => Array
(
)
)
[1] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
Output
Array
(
[0] => Array
(
[id] => 1
[name] => Gadgets
[code] => gadget
[parent_id] =>
)
[1] => Array
(
[id] => 2
[name] => Mobile
[code] => mobile
[parent_id] => 1
)
[2] => Array
(
[id] => 3
[name] => Laptops
[code] => laptop
[parent_id] => 1
)
[3] => Array
(
[id] => 4
[name] => Dell
[code] => dell
[parent_id] => 3
)
[4] => Array
(
[id] => 5
[name] => Lenovo
[code] => lenovo
[parent_id] => 3
)
)
Need help in making this type of array from the given array. I tried many things with for loops, but get stuck when in case there are many nested array and that solution does not fit correctly to my requirement.
There is one root node and others are child nodes and many parent nodes can have child nodes.

There are a ton of ways to do this, here are a couple of simple examples. If you don;t care about maintaining order, the recursive function is pretty simple. If you do need to maintain the order of the elements as they are encountered while traversing the tree (to render them as tables for example), it's just a bit more of a faff.
<?php
function flattenTree($array)
{
$output = [];
foreach($array as $currBranch)
{
if(!empty($currBranch['children']))
{
$children = flattenTree($currBranch['children']);
$output = array_merge($output, $children);
}
unset($currBranch['children']);
$output[] = $currBranch;
}
return $output;
}
function flattenTreeMaintainingOrder($array)
{
$output = [];
foreach($array as $currBranch)
{
$children = (array_key_exists('children', $currBranch)) ? $currBranch['children']:[];
unset($currBranch['children']);
$output[] = $currBranch;
if(!empty($children))
{
$children = flattenTreeMaintainingOrder($children);
$output = array_merge($output, $children);
}
}
return $output;
}
$flat = flattenTree($array);
$flatOrdered = flattenTreeMaintainingOrder($array);
print_r($flat) . PHP_EOL;
print_r($flatOrdered) . PHP_EOL;

A recursive function is one option...
function extractChildren($parent, $farr) {
$children = $parent['children'];
if (!$children || count($children)==0) return $farr;
unset($parent['children']);
$farr[]= $parent;
return extractChildren($children, $farr);
}
$finalarray=array();
// $array is the array you have in your question
foreach ($array as $parent) {
$finalarray = extractChildren($parent, $finalarray);
}
As #El_Vanya mentioned above, there are scads of other ways to accomplish this here: How to Flatten a Multidimensional Array?

Related

How to merge three arrays according to common key in php

I have three arrays first array include ids and employees name and second array have monthly collection with employee ids and third array have daily collection with employee id and daily collection I want to merge these array with ids and name and dcollection and monthly collection but the desired output is not coming here my first array $ids is
Array
(
[0] => stdClass Object
(
[id] => 1
[name] => Rohit
)
[1] => stdClass Object
(
[id] => 2
[name] => Emop1
)
[2] => stdClass Object
(
[id] => 3
[name] => Pankaj
)
[3] => stdClass Object
(
[id] => 4
[name] => tejpal singh
)
)
second array $q1 is
Array
(
[0] => stdClass Object
(
[name] => Rohit
[id] => 1
[mcollecton] => 100
)
[1] => stdClass Object
(
[name] => Emop1
[id] => 2
[mcollecton] => 1222
)
)
third array $q2 is
Array
(
[0] => stdClass Object
(
[name] => Rohit
[id] => 1
[dcollecton] => 300
)
[1] => stdClass Object
(
[name] => Emop1
[id] => 2
[dcollecton] => 150
)
)
so far what I have tried
$new_array = array();
foreach($ids as $k) {
$q1n = array("id"=>$k->id,"name"=>$k->name);
foreach($q1 as $k1) {
if($k->id==$k1->id){
$mc = array("mc"=>$k1->mcollecton);
array_merge($q1n,$mc);
}
}
foreach($q2 as $k1){
if($k->id==$k1->id){
$dc = array("dc"=>$k1->dcollecton);
array_merge($q1n,$dc);
}
}
$a = array_merge($q1n,$mc);
$av = array_merge($q1n,$dc);
array_push($new_array,$q1n);
}
but the output is coming as
Array
(
[0] => Array
(
[id] => 1
[name] => Rohit
)
[1] => Array
(
[id] => 2
[name] => Emop1
)
[2] => Array
(
[id] => 3
[name] => Pankaj
)
[3] => Array
(
[id] => 4
[name] => tejpal singh
)
)
I want the output be like
Array
(
[0] => Array
(
[id] => 1
[name] => Rohit
[mcollection] => 100
[dcollection] => 300
)
[1] => Array
(
[id] => 2
[name] => Emop1
[mcollection] => 1222
[dcollection] => 150
)
[2] => Array
(
[id] => 3
[name] => Pankaj
[mcollection] => 0
[dcollection] => 0
)
[3] => Array
(
[id] => 4
[name] => tejpal singh
[mcollection] => 0
[dcollection] => 0
)
)
So I have tried many times but the desired output is not coming . please help me out how to get the desired output.
It seemed like that answer could be modified, or put in a function that you could call multiple times if needed to combine more than two arrays.
There's probably cleaner ways to handle this with array functions like array_merge or array_walk, but this is the general idea of how I might approach it. I haven't tested this, but maybe it's useful.
foreach($first as $key1 => $value){
foreach($second as $key2 => $value2){
// match the ids and check if array key exists on first array
if($value['id'] === $value2['id'] && empty($first[$key2])){
$first[$key][$key2] = $value2;
}
}
}
EDIT: Based on the answer you posted vs the question you asked, are you incrementing the collection numbers or just setting them? In other words why use +=? You should also be able to remove array_merge and array_push.
Below is geared more towards what you're trying to do. I haven't tested this either, but if you run into errors, post your code with the errors returned so that it's easier to debug:
foreach($ids as $k)
{
$thisArray = $newArray[] = array("id"=>$k->id,"name"=>$k->name);
foreach($q1 as $k1)
{
if($k->id == $k1->id && !empty($k1->mcollecton))
{
$thisArray['mc'] = $k1->mcollecton;
}
}
foreach($q2 as $k2)
{
if($k->id == $k2->id && !empty($k2->dcollecton))
{
$thisArray['dc'] = $k2->dcollecton;
}
}
}
// This should have both new collections fields on all array items
print_r($newArray)

Assign parent ids to children of multidimensional arrays

I need to assign parent ids to all the children of a multidimensional array in PHP.
Array
(
[expanded] => 1
[key] => root_1
[title] => root
[children] => Array
(
[0] => Array
(
[folder] => 1
[key] => 34
[title] => YAY PROJECTS
)
[1] => Array
(
[expanded] => 1
[folder] => 1
[key] => 6
[title] => Grand Designs Episodes
[children] => Array
(
[0] => Array
(
[folder] => 1
[key] => 8
[title] => AU Episodes
)
[1] => Array
(
[expanded] => 1
[folder] => 1
[key] => 7
[title] => UK Episodes
[children] => Array
(
[0] => Array
(
[folder] =>
[key] => 9
[title] => Start something
)
[1] => Array
(
[folder] =>
[key] => 2
[title] => Grand Designs Season 10
)
)
)
)
)
[2] => Array
(
[expanded] => 1
[folder] => 1
[key] => 5
[title] => Animations
[children] => Array
(
[0] => Array
(
[folder] =>
[key] => 4
[title] => Futurama Episode 191
)
[1] => Array
(
[folder] =>
[key] => 3
[title] => Miniscule Series 5 Ep 1
)
[2] => Array
(
[folder] =>
[key] => 1
[title] => The Simpsons Episode 459
)
)
)
[3] => Array
(
[folder] => 1
[key] => 11
[title] => Test Folder
)
[4] => Array
(
[folder] => 1
[key] => 10
[title] => Testing
)
)
)
At first I thought this would be fairly trivial, however my solution quickly falls apart assigning the wrong parent_ids.
public function generateParentIds(array $input, $parentId = 0)
{
$return = [];
foreach ($input as $key => $value) {
if (is_array($value)) {
$value = $this->generateParentIds($value, $parentId);
if (isset($value['children'])) {
$parentId = $value['key'];
}
if (!is_int($key)) {
$return['parent_id'] = $parentId;
}
}
$return[$key] = $value;
}
return $return;
}
I'm not sure whats going on, I did a lot of research but couldn't find any examples, so I'd be very grateful for some help.
Assuming that each child should get the immediate parent's key value as its parent_id, this should do what you want. Note that it modifies the array in place ($input is passed by reference to the function, and the foreach loop uses a reference to $child), rather than attempting to merge returned values.
function generateParentIds(&$input, $parentId = 0) {
$input['parent_id'] = $parentId;
if (isset($input['children'])) {
foreach ($input['children'] as &$child) {
generateParentIds($child, $input['key']);
}
}
}
generateParentIds($input);
Output is too long to show here but there's a demo at 3v4l.org

How to count specific key values from a multi-dimensional array?

I have a vendor data array listed as a tree structure and each vendor have a type.
These are types of vendor and its id:
Agency = 1
Branch Agency = 2
Wholsaler = 3
Smartshop = 4
Example: ['type']=>2 (here this vendor is a branch agency).
My question is: How can I get the count of Branch agencies are in this array, same count of wholesaler and smart shop?
Desired result:
[2 => 2, 3 => 2, 4 => 1]
Here is my dynamic generated array:
Array
(
[2] => Array
(
[id] => 2
[type] => 2
[name] => R-1 Agency
[parent] => 1
[children] => Array
(
[3] => Array
(
[id] => 3
[type] => 3
[name] => R-1-W-1
[parent] => 2
[children] => Array
(
[11] => Array
(
[id] => 11
[type] => 4
[name] => mdf,lk
[parent] => 3
[children] => Array
(
)
)
)
)
)
)
[38] => Array
(
[id] => 38
[type] => 2
[name] => sndflk
[parent] => 1
[children] => Array
(
[40] => Array
(
[id] => 40
[type] => 3
[name] => new one
[parent] => 38
[children] => Array
(
)
)
)
)
)
I used this function :
function take_types($array){
foreach ($array as $key => $value) {
$types[] = $value['type'];
if(!empty($value['children'])){
$this->take_types($value['children']);
}
}
return $types;
}
When I use the above function the output is like this:
Array
(
[0] => 2
[1] => 2
)
I only get two values, I need to get the count of each vendor type.
There will be many techniques to recursively process your tree data. I'll offer a native function style and a custom recursive style.
array_walk_recursive() visits all of the "leaf nodes", so you only need to check the key and push the value into a variable which can be accessed outside of that function's scope -- this is why "modifying by reference" is vital.
Code: (Demo)
// I removed the chunky $tree declaration from my post, see the demo
$result = [];
array_walk_recursive(
$tree,
function($v, $k) use (&$result) {
if ($k === 'type') {
$result[] = $v;
}
}
);
var_export(array_count_values($result));
Or
function recursiveTypeCount($array, $output = []) {
foreach($array as $item) {
if (!isset($output[$item['type']])) {
$output[$item['type']] = 1;
} else {
++$output[$item['type']];
}
if ($item['children']) {
$output = recursiveTypeCount($item['children'], $output);
}
}
return $output;
}
var_export(recursiveTypeCount($tree));
Both will display:
array (
2 => 2,
3 => 2,
4 => 1,
)

Copying a multi dimensional array of nodes into another array

I am looking to convert a multi dimensional array into another multidimensional array using a recursive function.
Source array :
Array
(
[1] => Array
(
[id] => 1
[source_name] => kk56ca1d0f2378f
[company_id] => 1
[lft] => 1
[rgt] => 18
[parent_id] => 0
[children] => Array
(
[2] => Array
(
[id] => 2
[source_name] => kk56ca1d17f3f63
[company_id] => 1
[lft] => 2
[rgt] => 3
[parent_id] => 1
[children] => Array
(
)
)
[3] => Array
(
[id] => 3
[source_name] => kk56ca1d1ebe975
[company_id] => 1
[lft] => 4
[rgt] => 13
[parent_id] => 1
[children] => Array
(
[6] => Array
(
[id] => 6
[source_name] => kk56ca1fc882ac0
[company_id] => 1
[lft] => 5
[rgt] => 10
[parent_id] => 3
[children] => Array
(
)
)
)
)
)
)
)
which I need to get into the format of
Array
(
[0] => Array
(
[id] => 1
[text] => kk56ca1d0f2378f
[parent_id] => 0
[nodes] => Array
(
[0] => Array
(
[id] => 2
[text] => kk56ca1d17f3f63
[parent_id] => 1
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 3
[text] => kk56ca1d1ebe975
[parent_id] => 1
[nodes] => Array
(
[0] => Array
(
[id] => 6
[text] => kk56ca1fc882ac0
[parent_id] => 3
[nodes] => Array
(
)
)
[1] => Array
(
[id] => 15
[text] => kk
[parent_id] => 3
[nodes] => Array
(
)
)
)
)
)
)
)
I have been trying for hours and getting nowhere with this. Any help would be really appreciated.
The source array has associative indexes (though they are numbers) and the destination array has numerical indexes. Besides this, just need to remove a few indexes and change names of a few.
EDIT :
Specific changes :
change index name source_name to text
change index name children to nodes
unset indexes lft, rgt, company_id
I do not have much experience with recursion so I have trying fruitlessly.
This is what I could come up with :
// pass array of nodes
function convert_array($from){
// this is a node
if(isset($from['source_name']))
{
$temp = array();
$temp['id'] = $from['id'];
convert_array($from['children']);
}
// this is an array of nodes
else
{
foreach($from as $arr)
{
$ret = convert_array($arr);
print_r($ret);
}
}
}
But I am not able to understand what data to be returned and how the new array builds up from the return values.
Here the working function:
function convert_array( $array )
{
$retval = array();
foreach( $array as $row )
{
$child = array();
$child['id'] = $row['id'];
$child['text'] = $row['source_name'];
$child['parent_id'] = $row['parent_id'];
if( count( $row['children'] ) )
{ $child['nodes'] = convert_array( $row['children'] ); }
else
{ $child['nodes'] = array(); }
$retval[] = $child;
}
return $retval;
}
3v4l demo
I think it is self-explanatory, BTW: we init an empty array ($retval), then we perform a foreach loop through all array argument: for each element, we init a new array and we add it id, source_name as text and parent_id; if the children index has elements, we perform a recursive call to fill nodes array index, otherwise we set it to empty array.

Get all parent nodes with RecursiveArrayIterator

Essentially, I want to use the
$foo = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack));
Methodology, but instead of returning a flat array for foreach()ing through, keep the structure but only return a single great-grand-child node and it's parent nodes. Is this possible in PHP?
I've been tasked with optimising some of my company's (horrific) codebase. I found a function that recurses through an array, searching for a key. I can't replace this with a simple array_search() or array_key_exists() because the custom function also returns the parent nodes of the matched (found) key, instead of just a true or false.
How can I use RecursiveArrayIterator, RecursiveIteratorIterator or failing that, other built-in functions (i.e. as little custom code as possible) to return a matching node with it's parent tree from a search array? I want to get the fastest function possible, as currently this function spends 8 seconds executing 14 thousand times, hence my requirement to use built-in functions.
My existing attempt (below) is incredibly slow.
function search_in_array($needle, $haystack) {
$path = array();
$it = new RecursiveArrayIterator($haystack);
iterator_apply($it, 'traverse', array($it, $needle, &$path));
return $path;
}
function traverse($it, $needle, &$path) {
while($it->valid()) {
$key = $it->key();
$value = $it->current();
if(strcasecmp($value['id'], $needle) === 0) {
$path[] = $key;
return;
} else if($it->hasChildren()) {
$sub = null;
traverse($it->getChildren(), $needle, &$sub);
if($sub) {
$path[$key] = $sub;
}
}
$it->next();
}
}
Example output for $needle = TVALL would look like this:
Array (
[HOMECINEMA] => Array (
[children] => Array (
[HC-VISION] => Array (
[children] => Array (
[0] => TVALL
)
)
)
)
)
The search array looks something like this (sorry for the vast-ness). There are more than two top-level nodes, but I've truncated it for brevity:
Array(2) (
[HOMECINEMA] => Array (
[id] => HOMECINEMA
[position] => 2
[title] => TV & Home Cinema
[children] => Array (
[HC-VISION] => Array (
[id] => HC-VISION
[title] => LCD & Plasma
[children] => Array (
[TVALL] => Array (
[id] => TVALL
[title] => All TVs
)
[LCD2] => Array (
[id] => LCD2
[title] => LCD/LED TVs
)
[PLASMA] => Array (
[id] => PLASMA
[title] => Plasma TVs
)
[3DTV] => Array (
[id] => 3DTV
[title] => 3D TV
)
[LED] => Array (
[id] => LED
[title] => SMART TVs
)
[PROJECTORS] => Array (
[id] => PROJECTORS
[title] => Projectors
)
[SYS-HOMECINEMATV] => Array (
[id] => SYS-HOMECINEMATV
[title] => TV Home Cinema Systems
)
)
)
[HC-SEPARATES] => Array (
[id] => HC-SEPARATES
[title] => Home Cinema Separates
[children] => Array (
[DVDRECORDERS] => Array (
[id] => DVDRECORDERS
[title] => DVD Recorders
)
[HDDVD] => Array (
[id] => HDDVD
[title] => Blu-ray
)
[AVRECEIVERS] => Array (
[id] => AVRECEIVERS
[title] => AV Receivers
)
[DVDPLAYERS] => Array (
[id] => DVDPLAYERS
[title] => DVD Players
)
[FREEVIEW] => Array (
[id] => FREEVIEW
[title] => Digital Set Top Boxes
)
[DVDPACKAGESYSTEMS-3] => Array (
[id] => DVDPACKAGESYSTEMS-3
[title] => 1 Box Home Cinema Systems
)
[HOMECINEMADEALS] => Array (
[id] => HOMECINEMADEALS
[title] => Home Cinema System Deals
)
)
)
[SPEAKER2] => Array (
[id] => SPEAKER2
[title] => Speakers
[children] => Array (
[SPEAKERPACKAGES] => Array (
[id] => SPEAKERPACKAGES
[title] => Speaker packages
)
[SOUNDBARS] => Array (
[id] => SOUNDBARS
[title] => Soundbars
)
[CENTRES] => Array (
[id] => CENTRES
[title] => Centres
)
[SUBWOOFERS] => Array (
[id] => SUBWOOFERS
[title] => Subwoofers
)
[FLOORSTANDING] => Array (
[id] => FLOORSTANDING
[title] => Floorstanders
)
[INSTALLATIONSPEAKERS] => Array (
[id] => INSTALLATIONSPEAKERS
[title] => Installation Speakers
)
[STAND-MOUNT] => Array (
[id] => STAND-MOUNT
[title] => Bookshelf Speakers
)
)
)
[HC-ACCYS] => Array (
[id] => HC-ACCYS
[title] => Accessories
[children] => Array (
[AVESSENTIALS] => Array (
[id] => AVESSENTIALS
[title] => AV Interconnects
)
[PLASMALCDSTANDSBRACKETS1] => Array (
[id] => PLASMALCDSTANDSBRACKETS1
[title] => TV Accessories
)
[RACKS] => Array (
[id] => RACKS
[title] => TV Racks
)
[HIFIRACKS] => Array (
[id] => HIFIRACKS
[title] => HiFi Racks
)
[PROJECTORACCYS] => Array (
[id] => PROJECTORACCYS
[title] => Projector Screens/Accessories
)
)
)
)
)
[SPEAKERS] => Array (
[id] => SPEAKERS
[position] => 3
[title] => Speakers
[children] => Array (
[SPK-HIFI] => Array (
[id] => SPK-HIFI
[title] => Hi-Fi
[children] => Array (
[STAND-MOUNT] => Array (
[id] => STAND-MOUNT
[title] => Bookshelf Speakers
)
[FLOORSTANDING] => Array (
[id] => FLOORSTANDING
[title] => Floorstanders
)
[INSTALLATIONSPEAKERS] => Array (
[id] => INSTALLATIONSPEAKERS
[title] => Installation Speakers
)
)
)
[SPK-HOMECINEMA] => Array (
[id] => SPK-HOMECINEMA
[title] => Home Cinema
[children] => Array (
[SPEAKERPACKAGES] => Array (
[id] => SPEAKERPACKAGES
[title] => Speaker Packages
)
[SOUNDBARS] => Array (
[id] => SOUNDBARS
[title] => Soundbars
)
[CENTRES] => Array (
[id] => CENTRES
[title] => Centres
)
[SUBWOOFERS] => Array (
[id] => SUBWOOFERS
[title] => Subwoofers
)
)
)
[SPK-ACCYS] => Array (
[id] => SPK-ACCYS
[title] => Accessories
[children] => Array (
[SPEAKERESSENTIALS1] => Array (
[id] => SPEAKERESSENTIALS
[title] => Speaker Cables
)
[SPEAKERSTANDS] => Array (
[id] => SPEAKERSTANDS
[title] => Speaker Stands
)
[SPEAKERBRACKETS] => Array (
[id] => SPEAKERBRACKETS
[title] => Speaker Wall Brackets
)
)
)
)
)
)
The example below will not necessarily be more performant (in time or memory requirements) but avoids manually recursing through the structure and shows an easier (IMHO) way to build your desired output array.
function search_in_array($needle, $haystack) {
$path = array();
$it = new RecursiveIteratorIterator(
new ParentIterator(new RecursiveArrayIterator($haystack)),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($it as $key => $value) {
if (array_key_exists('id', $value) && strcasecmp($value['id'], $needle) === 0) {
$path = array($needle);
for ($i = $it->getDepth() - 1; $i >= 0; $i--) {
$path = array($it->getSubIterator($i)->key() => $path);
}
break;
}
}
return $path;
}
Reference
RecursiveIteratorIterator::SELF_FIRST iteration mode - required to see non-"leaf" items
ParentIterator - easy way to filter out the "leaf" items
RecursiveIteratorIterator::getDepth()
RecursiveIteratorIterator::getSubIterator()
Bonus
You could also use the RecursiveIteratorIterator::setMaxDepth() method to limit the recursion to n levels deep, if your array also goes much deeper.

Categories