PHP - Convert flat array of data to layered multidimensional array - php

Here is my data returned from a CMS function:
array (
0 =>
stdClass::__set_state(array(
'term_id' => '31',
'parent' => '26'
)),
1 =>
stdClass::__set_state(array(
'term_id' => '3',
'parent' => '0'
)),
2 =>
stdClass::__set_state(array(
'term_id' => '32',
'parent' => '26'
)),
3 =>
stdClass::__set_state(array(
'term_id' => '33',
'parent' => '26'
)),
4 =>
stdClass::__set_state(array(
'term_id' => '34',
'parent' => '26'
)),
5 =>
stdClass::__set_state(array(
'term_id' => '26',
'parent' => '3'
)),
I need to convert the above to the following format:
Array
(
[0] => Array
(
3
)
[1] => Array
(
26
)
[2] => Array
(
31
32
33
34
)
)
So let me explain. Each item in the source is a term. Each term has an ID (term_id) and a parent (parent). If the parent == 0 it doesn't have a parent and is level 0. Any child items of level 0 are level 1 and so on.
What I want to return is an array that holds all the levels and its ID's at that level.
Be aware that this is just sample data and there could be many more levels and any number of ID's on each level.
So using PHP how do I achieve what I'm after?

Assuming valid structure (no orphans and such), can loop through data and pluck each next level, until no more data left:
$output = array();
$current = array( 0 );
$index = 0;
while ( ! empty( $data ) ) {
$parents = $current;
$current = array();
$output[$index] = array();
foreach ( $data as $key => $term ) {
if ( in_array( $term->parent, $parents ) ) {
$output[$index][] = $term->term_id;
$current[] = $term->term_id;
unset( $data[$key] );
}
}
$index ++;
}
var_dump( $output );

Whipped this together real quick, kind of dirty but I think it's what you need.
http://snipt.org/vagU0

I think you need more data. For example objects with parent = 26 are at level 2. How do you now that? You need to have another array where you will have parent_id and its level. Then you can iterate through array and build what you want. If this is multidemnsional array you can use array_walk_recursive

Related

Loop into multidimensional array from top to bottom

I have this tree :
Array
(
[0] => Array
(
[id] => 1
[parent_id] => 0
[title] => Parent Page
[children] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
[title] => Sub Page
),
[1] => Array
(
[id] => 5
[parent_id] => 1
[title] => Sub Page 2
)
)
)
[1] => Array
(
[id] => 4
[parent_id] => 0
[title] => Another Parent Page
)
)
And I'm looking for a display from top to bottom.
And display something like this :
1
1.2
1.5
4
But if I have id 3 which is a leaf from 5 I would like this :
1
1.2
1.5
1.5.3
4
I have search a lot and my brain is limited when i'm using recursivity..
I have tried this :
function printAll($a){
foreach ($a as $v){
if (!array_key_exists('children', $v)){
debugLog($v['id']);
return;
}
else{
$arrayChildrens = $v['children'];
foreach($arrayChildrens as $c){
$arrayChildrens = $c['children'];
$this->printAll($arrayChildrens);
}
}
}
}
But doesn't work..
I tried to begin just to display
1
2
5
4
But my goal is to display id parents before id ( like Ishowed you before)
Thanks a lot !
This function should give you your expected output.
function printAll($a, $prefix = '') {
//loop through $a
foreach($a as $v) {
//echo current level `id` with previous `$prefix`
echo "{$prefix}{$v['id']}\n";
//check if current level contains children
if(!empty($v['children'])) {
//clean up prefix to remove extra `.` at the end of prefixes
$prev_prefix = rtrim($prefix, '.');
//recurse printAll again passing the children as `$a` and a `$prefix` being the previous levels combined e.g `1.5`
//also clean up extra periods at the start of the prefix
printAll($v['children'], ltrim("{$prev_prefix}.{$v['id']}.", "."));
}
}
}
Output:
1
1.2
1.5
1.5.3
4
Using a proper return
Usually with a function you actually want the function to return values instead of echoing them automatically to your page. If you want this function to return an array of values instead of echoing them, you could do this:
function printAll($a, $level = '', $values = []) {
foreach($a as $v) {
$values[] = $value = "{$level}{$v['id']}";
if(!empty($v['children'])) {
$values = printAll($v['children'], "{$value}.", $values);
}
}
return $values;
}
Which will have a result like this:
Array
(
[0] => 1
[1] => 1.2
[2] => 1.5
[3] => 1.5.3
[4] => 4
)
This should do the job.
$arr = array(
array(
'id' => 1,
'parent_id' => 0,
'title' => 'Parent Page',
'children' => array(
array(
'id' => 2,
'parent_id' => 1,
'title' => 'Sub Page',
),
array(
'id' => 5,
'parent_id' => 1,
'title' => 'Sub Page 2',
'children' => array(
array(
'id' => 7,
'parent_id' => 5,
'title' => 'Sub Page',
),
array(
'id' => 8,
'parent_id' => 5,
'title' => 'Sub Page 2',
)
)
)
)
),
array(
'id' => 4,
'parent_id' => 0,
'title' => 'Another Parent Page',
)
);
function printAll($arr, $parent = [])
{
if (is_array($arr)) {
foreach ($arr as $k => $v) {
if (isset($v['id'])) {
$parent[] = $v['id'];
echo implode('.', $parent) . PHP_EOL;
}
if (isset($v['children'])) {
printAll($v['children'], $parent);
}
array_pop($parent);
}
}
}
printAll($arr);
Output
1
1.2
1.5
1.5.7
1.5.8
4
Working demo.

Prune/reduce a multi-dimensional array in PHP to a certain depth

I've searched for this on and off for a couple of years to no avail. Occasionally I'd like to prune a multi-dimension to a certain depth. This would be useful when storing large recursive data sets when you only need data to a certain depth and the rest should be discarded.
For example, given the following array:
$arr = array(
'1' => array(
'1.1' => '1.1.1'),
'2',
'3' => array(
'3.1' => array(
'3.1.1' => '3.1.1.1')
)
);
I would want to prune it back to x number of dimensions calling something like this:
some_prune_method($arr array, $dimensions integer)
...and expect data to be returned something like this:
print_r(some_prune_method($arr, 1));
Array(
[
'1',
'2',
'3'
]
)
print_r(some_prune_method($arr, 2));
Array(
[
'1' => ['1.1'],
'2',
'3' => ['3.1']
]
)
print_r(some_prune_method($arr, 3));
Array(
[
'1' => ['1.1'],
'2',
'3' => ['3.1' => '3.1.1']
]
)
I can think how to do this manually using a callback to iterate through the array but that's slow. Ideally this would need to be done WITHOUT iterating through the entire array.
Maybe I'm missing a simple way to do this?
Think this is what your after. The main concept is just a recursive method to copy the input array which keeps track of the depth. Each time it goes back round it takes 1 off the depth and just before checking if it needs to call the recursive loop it checks if the depth will allow it. If not then it just adds the key to the output loop instead.
$arr = array(
'1' => array(
'1.1' => '1.1.1'),
'2',
'3' => array(
'3.1' => array(
'3.1.1' => '3.1.1.1')
)
);
function some_prune_method ( $array, $depth ) {
$output = [];
foreach ( $array as $key => $element ) {
if ( is_array($element)) {
if ( $depth > 1 ) {
$output [$key] = some_prune_method ( $element, $depth-1 );
}
else {
$output[] = $key;
}
}
else {
$output[] = $element;
}
}
return $output;
}
print_r(some_prune_method($arr, 2));
gives...
Array
(
[1] => Array
(
[0] => 1.1.1
)
[2] => 2
[3] => Array
(
[0] => 3.1
)
)

Group data sets by specific column where column names are the first row in each set

I have an array of arrays like this:
$data = array (
'data1' => array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
),
),
'data2' => array (
0 =>
array (
0 => 'Balance',
1 => 'ID',
),
1 =>
array (
0 => '4533',
1 => '101',
)
),
'data3' => array (
0 =>
array (
0 => 'Active',
1 => 'ID',
),
1 =>
array (
0 => 'Yes',
1 => '101',
),
2 =>
array (
0 => 'No',
1 => '103',
)
),
);
In the $data array there are three arrays named data1, data2 and data3 respectively.
In each array, the first row is the name of the columns and the remaining rows are the values for those columns (think of it like a table).
In each data1,data2 and data3 the first row contains a column called ID.
I want to group the data from all three arrays based on the matching ID field such that the final output array is like this:
Desired Output:
$output =
array (
'output' =>
array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
3 => 'Balance',
4 => 'Active',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
3 => '4533',
4 => 'Yes',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
3 => 'null',
4 => 'No',
),
)
);
What I tried(just a attempt to combine data1 and data2) :
$d1=$data['data1'];
$d2=$data['data2'];
if(count($d1)>count($d2))
{
$arr1=array();
$arr2=array();
$arr3=array();
$column1=$d1[0];
$column2=$d2[0];
for($i=1;$i<=(count($d1)-1);$i++)
{
if($i<count($d2))
$arr2[]=array_combine($column2,$d2[$i]);
else
$arr2[]=array_combine($column2,array('0'=>'','1'=>''));
}
for($i=1;$i<=(count($d1)-1);$i++)
{
$arr1[]=array_combine($column1,$d1[$i]);
}
for($i=0;$i<=(count($arr1)-1);$i++)
{
$arr3[]=array_merge($arr1[$i],$arr2[$i]);
}
print_r($arr3);
}
I need help regarding a neat code to combine any number of arrays.
Notice that missing elements should receive a null value.
How do I get the output I have mentioned above?
This splits it into 2 steps, first accumulate all the data by the ID, also all the header columns are collected. Then use this data to create output array with blanks where the data is missing.
Comments in code...
$store = [];
$headers = [];
foreach ( $data as $set ) {
$headerRow = array_shift($set);
// Collect all header columns
$headers = array_merge($headers, $headerRow);
foreach ( $set as $index => $list ){
// Create associative list of data so they can be combined (i.e. ID fields)
$list = array_combine($headerRow, $list);
// Use ID value as key and create if needed
if ( !isset($store[$list["ID"]]) ) {
$store[$list["ID"]] = $list;
}
else {
$store[$list["ID"]] = array_merge($store[$list["ID"]], $list);
}
}
}
$headers = array_unique($headers);
$output = [ 'output' => [$headers]];
// Create template array, so that missing fields will be set to null
$blank = array_fill_keys($headers, null);
foreach ( $store as $dataRow ) {
// Fill in the fields for this ID and then change to numeric keys
$output['output'][] = array_values(array_merge($blank, $dataRow));
}
Having the keys as the first element of array is not good practice - that why keys are for.
I recommend different approach - use array-combine for connect them and use the ID as key:
foreach($data as $v) {
$keys = array_shift($v); // take the keys
foreach($v as &$e) {
$e = array_combine($keys, $e); // combine the keys and the value
// add or append them according the ID
if (!isset($res[$e['ID']])) $res[$e['ID']] = $e;
else $res[$e['ID']] = array_merge($res[$e['ID']], $e);
}
}
Now you can take this - and if you must convert it back to your structure.
Live example: 3v4l
Largely resembling NigelRen's answer, my snippet will group by ID value, apply default null values where appropriate and prepend a row of column names after all other array building is finished.
Code: (Demo)
$result = [];
$uniqueKeys = [];
// group data by ID
foreach ($data as $rows) {
$keyRow = array_shift($rows);
$uniqueKeys += array_combine($keyRow, $keyRow);
foreach ($rows as $row) {
$assoc = array_combine($keyRow, $row);
$result[$assoc['ID']] = ($result[$assoc['ID']] ?? []) + $assoc;
}
}
// apply default null values where missing element and re-index rows
foreach ($result as &$row) {
$row = array_values(
array_replace(
array_fill_keys($uniqueKeys, null),
$row
)
);
}
// prepend row of column names
array_unshift($result, array_values($uniqueKeys));
var_export($result);
Output:
array (
0 =>
array (
0 => 'ID',
1 => 'PinCode',
2 => 'Date',
3 => 'Balance',
4 => 'Active',
),
1 =>
array (
0 => '101',
1 => '454075',
2 => '2012-03-03',
3 => '4533',
4 => 'Yes',
),
2 =>
array (
0 => '103',
1 => '786075',
2 => '2012-09-05',
3 => NULL,
4 => 'No',
),
)

php convert array into a hierarchical nested set for database

So I read this:
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
And i'm using mysql and php
What I need is script that convert only for one time the old data into data with the left/right values.
I don't need to add/update things.
The php data
$in_array = array (
array(
'id' => 400,
'n' => 'Sub 1a',
's' => array (
array (
'n' => 'Sub 1b',
'id' => 421,
's' => array (
array (
'n' => 'Sub 1c',
'id' => 422,
)
)
)
)
),
array(
'id' => 500,
'n' => 'Sub 2a',
's' => array (
array (
'n' => 'Sub 2b',
'id' => 521,
's' => array (
array (
'n' => 'Sub 3b',
'id' => 522,
)
)
)
)
)
);
At the moment i'm trying to solve this with this script but that don't work like it should.
$a_newTree = array();
function rebuild_tree($parent, $left) {
global $a_newTree;
$indexed = array();
// the right value of this node is the left value + 1
$right = $left+1;
// get all children of this node
foreach($parent as $cat){
// recursive execution of this function for each
// child of this node
// $right is the current right value, which is
// incremented by the rebuild_tree function
if(is_array($cat) && array_key_exists('s',$cat)){
$indexed['n'] = $cat['n'];
$right = rebuild_tree($cat, $right);
}
}
// we've got the left value, and now that we've processed
// the children of this node we also know the right value
array_push($a_newTree,array(':lft'=>$left,':rgt'=>$right,':name'=>'bla'));
// return the right value of this node + 1
return $right+1;
}
rebuild_tree($in_array, 1);
I can access mysql and query so the output is like this
Sub 1a | Sub 1b | Sub 1c
Sub 1a | Sub 1b | Sub 1d
Sub 1a | Sub 1b | Sub 1e
Sub 1a | Sub 1f | Null
Sub 1a | Sub 1g | Null
Sub 2a | Sub 2b | Sub 2c
With that data I made the array above.
The array was not oke.
This is working
$in_array = array (
// array(
'id' => 400,
'n' => 'Sub 1a',
's' => array (
// array (
'n' => 'Sub 1b',
'id' => 421,
's' => array (
// array (
'n' => 'Sub 1c',
'id' => 422,
// )
)
// )
// )
),
array(
'id' => 500,
'n' => 'Sub 2a',
's' => array (
// array (
'n' => 'Sub 2b',
'id' => 521,
's' => array (
// array (
'n' => 'Sub 3b',
'id' => 522,
// )
)
// )
)
)
);
$a_newTree = array();
function rebuild_tree($parent, $left) {
global $a_newTree;
$indexed = array();
$right = $left+1;
if(array_key_exists('n',$parent)){
$indexed['n'] = $parent['n'];
}else{
$indexed['n'] = '?';
}
foreach($parent as $cat){
if(is_array($cat)){
$right = rebuild_tree($cat, $right);
}
}
array_push($a_newTree,array(':lft'=>$left,':rgt'=>$right,':name'=>$indexed['n']));
return $right+1;
}
rebuild_tree($in_array, 1);

(PHP) Converting an array of arrays from one format into another

I currently have an array, created from a database, an example of which looks like the following:
Array(
[0] => Array (
objectid => 2,
name => title,
value => apple
),
[1] => Array (
objectid => 2,
name => colour,
value => red
),
[2] => Array (
objectid => 3,
name => title,
value => pear
),
[3] => Array (
objectid => 3,
name => colour,
value => green
)
)
What I would like to do is group all the items in the array by their objectid, and convert the 'name' values into keys and 'value' values into values of an associative array....like below:
Array (
[0] => Array (
objectid => 2,
title => apple,
colour => red
),
[1] => Array (
objectid => 3,
title => pear,
colour => green
)
)
I've tried a few things but haven't really got anywhere.. Any ideas?
Thanks in advance
This should work with your current setup and should be able to handle as many key-value pairs as available:
<?php
$results = array(
array('objectid' => 2, 'name' => 'title', 'value' => 'apple'),
array('objectid' => 2, 'name' => 'color', 'value' => 'red'),
array('objectid' => 3, 'name' => 'title', 'value' => 'pear'),
array('objectid' => 3, 'name' => 'color', 'value' => 'green'));
$final = array();
foreach ($results as $result) {
$final[$result['objectid']]['objectid'] = $result['objectid'];
$final[$result['objectid']][$result['name']] = $result['value'];
}
print_r($final);
It would be easier to have the array keys correspond with your object id, that way you can iterate through your existing array, and add the key-value pairs for each object, like so:
$newArray = array();
foreach ($results as $result) {
if (!array_key_exists($result['objectid'], $newArray)) {
$newArray[$result['objectid'] = array();
}
foreach ($result as $key => $value) {
$newArray[$result['objectid'][$key] = $value;
}
}
Peter's method is perfectly valid, I just thought I would show a shorter version of the same thing (couldn't do in a comment)
foreach( $array as $obj ) {
if( !isset( $objects[$obj['objectid']] ) )
$objects[$obj['objectid']]['objectid'] = $obj['objectid'];
$objects[$obj['objectid']][$obj['name']] = $obj['value'];
}

Categories