I have a PHP multi-dimensional array organized with each node listing its parent nodes under it. I'm trying to transform the array so that the output lists hierarchically with each node listing any child nodes and only listing unique paths within the array.
for instance this input array:
$input = [
[
"name" => "home",
"parents" => [],
],
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "newsdetail",
"parents" => [
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "knowledge",
"parents" => [],
],
];
Should output this array:
$output = [
[
"name" => "home",
"children" => [
[
"name" => "newslist",
"children" => [
[
"name" => "newsdetail",
"children" => [],
],
],
],
],
],
[
"name" => "knowledge",
"children" => [],
],
];
This could probably be done in a much nicer way, but this method works. just procedural functions as a proof of concept.
<?php
$input = [
[
"name" => "home",
"parents" => [],
],
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "newsdetail",
"parents" => [
[
"name" => "newslist",
"parents" => [
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "home",
"parents" => [],
],
],
],
[
"name" => "knowledge",
"parents" => [],
],
];
//recursively get all parents and the level the parent is at
function getParents($nodes,$level,&$parents)
{
foreach($nodes AS $key => $node)
{
$parents[ $node['name'] ] = array( "name" => $node['name'], "level" => $level);
if(isset($node['parents']) && !empty($node['parents']))
{
$level += 1;
getParents($node['parents'],$level,$parents);
}
}
}
//sort the parents by level
function sortParentsByLevel($a, $b)
{
if ($a['level'] == $b['level']) {
return 0;
}
return ($a['level'] > $b['level']) ? -1 : 1;
}
//find the output path based on parents array to add new value to
function setValueFromPath(&$paths, $parents, $value)
{
$dest = &$paths;
if(empty($parents))
{
if(!isset($dest[$value]))
$dest[$value] = array();
} else {
$finalNode = array_pop($parents);
foreach ($parents as $parent)
{
$dest = &$dest[$parent];
}
$dest[$finalNode][$value] = array();
}
}
//init new variable
$output = array();
//loop through each input node
foreach($input AS $key => $node)
{
//init a parent array
$parents = array();
//if we have parents use the getParents method to set them
if(isset($node['parents']) && is_array($node['parents']) && !empty($node['parents']))
{
getParents($node['parents'],1,$parents);
}
//sort the parents according to their level
uasort($parents, 'sortParentsByLevel');
//we're only interested in the associative key
$parentKeys = array();
foreach($parents AS $parent)
{
$parentKeys[] = $parent['name'];
}
//add the $node['name'] value in the appropriate parent array
setValueFromPath($output, $parentKeys, $node['name'] );
}
echo '<pre>';
print_r($output);
echo '</pre>';
die();
/*
Array
(
[home] => Array
(
[newslist] => Array
(
[newsdetail] => Array
(
)
)
)
[knowledge] => Array
(
)
)
*/
Related
I have a function which gives an array of the below format
$result = [
[
"name" => "text",
"id" => "928610",
"entity_type" => "node"
],
[
"name" => "folder",
"id" => "987620",
"entity_type" => "folder"
],
[
"name" => "text",
"id" => "956720",
"entity_type" => "node"
],
];
Each Folder "entity_type" => "folder" item has again child which returns same format array.
like if we run a foreach loop $result and if it is "entity_type" => "folder" then we pass the id to a function it will also give a similar array format as that of result.
So i need if it is "entity_type" => "folder" the below key added to the "entity_type" => "folder" item
"children" => [
'#theme' => 'child_elements',
'#child_elements' => [
[
'name' => 'text',
"id" => "333421",
"entity_type" => "node"
],
[
'name' => 'folder',
"id" => "897622",
"entity_type" => "folder"
],
[
'name' => 'text',
"id" => "342214",
"entity_type" => "node"
],
],
],
and recursively it should keep on adding if "entity_type" => "folder"
The final array should be
$result = [
[
"name" => "text",
"id" => "928610",
"entity_type" => "node"
],
[
"name" => "folder",
"id" => "987620",
"entity_type" => "folder"
"children" => [
'#theme' => 'child_elements',
'#child_elements' => [
[
'name' => 'text',
"id" => "333421",
"entity_type" => "node"
],
[
'name' => 'folder',
"id" => "897622",
"entity_type" => "folder"
],
[
'name' => 'text',
"id" => "342214",
"entity_type" => "node"
],
],
],
],
[
"name" => "text",
"id" => "956720",
"entity_type" => "node"
],
];
public function buildTree($elements) {
$branch = [];
$branch = ['#theme' => 'child_elements'];
foreach ($elements as $key => $element) {
foreach($element as $keys => $values){
if ($element['bundle'] == 'folder') {
$child = $this->loadElements($element['id']);
$branch['#child_elements'] = $child;
$element[$key]['children'] = $branch['#child_elements'];
$this->buildTree($child);
array_push($branch, $element);
}else{
$branch['#child_elements'][] = $element;
}
}
}
return $branch;
}
you need to use some logic statement and use array_push when the condition is met.
Is it possible to stop the loop from overwriting the previous value?
<?php
foreach (array('email1', 'email2') as $line) {
$x = [
$line => [
"Reports" => [
(object) [
"ReportType" => "1",
"SummaryFrequency" => [
(object) [
"FrequencyType" => "8011",
"SecondsPast" => "32400",
],
],
"Filter" => (object) [
"ClauseType" => "or",
"RuleField" => "",
"RuleOperator" => "",
"RuleValue" => "",
"ClauseChildren" => [
(object) [
"ClauseType" => "",
"RuleField" => "BackupJobDetail.TimeSinceStarted",
"RuleOperator" => "int_lte",
"RuleValue" => "86400",
],
],
],
],
],
],
];
}
print_r($x);
https://phpize.online/?phpses=e658de0de3dc1ed5a4d8d27ecebf567a&sqlses=null&php_version=php8&sql_version=mysql57
At the moment you overwrite $x each time, if you want to make it an array with $line as each index, create an empty array and then add the new items in with new index...
$x = [];
foreach (array('email1', 'email2') as $line) {
$x[$line] = [
"Reports" => [
(object) [
I have a problem with multidimensional data arrays. I have a data array like this:
[
[
"name" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072"
"status" => "current"
], [
"name" => "netSnmpObjects",
"oid" => "1.3.6.1.4.1.8072.1"
], [
"name" => "netSnmpEnumerations",
"oid" => "1.3.6.1.4.1.8072.3"
], [
"name" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"name" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"name" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
], [
"name" => "netSnmpNotificationPrefix",
"oid" => "1.3.6.1.4.1.8072.4"
], [
"name" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"name" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
]
I am looking for a simple way to create an array tree, based on the oid value from the array above. Those oid values are a dot-separated path. The more parts, the deeper in the final tree the corresponding item should be put.
The desired output:
[
"text" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072",
"nodes" => [
[
"oid" => "1.3.6.1.4.1.8072.1",
"text" => "netSnmpObjects"
], [
"oid" => "1.3.6.1.4.1.8072.3",
"text" => "netSnmpEnumerations",
"nodes" => [
[
"text" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"text" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"text" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
]
]
], [
"oid" => "1.3.6.1.4.1.8072.4",
"text" => "netSnmpNotificationPrefix"
"nodes" => [
[
"text" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"text" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
]
]
]
]
Any idea how I can solve it?
You can key your data by oid in a new associated array: that way you can look up a certain parent key in a fast way.
Then it is just a matter of getting the parent key of a any given key (chopping off the final dot-separated part) and see where you have it in your new associated array, and then appending it to its nodes array.
Here is a function that does that:
function makeTree($arr) {
$keyed = [];
foreach ($arr as $item) $keyed[$item["oid"]] = $item;
$root = null;
foreach ($keyed as &$item) {
$key = $item["oid"];
$parent = substr($key, 0, strrpos($key, "."));
if (isset($keyed[$parent])) {
$keyed[$parent]["nodes"][] =& $item;
} else {
$root = $key;
}
}
return $keyed[$root];
}
You can run it like this on your sample data:
$arr = [
[
"name" => "netSnmp",
"oid" => "1.3.6.1.4.1.8072",
"status" => "current"
], [
"name" => "netSnmpObjects",
"oid" => "1.3.6.1.4.1.8072.1"
], [
"name" => "netSnmpEnumerations",
"oid" => "1.3.6.1.4.1.8072.3"
], [
"name" => "netSnmpModuleIDs",
"oid" => "1.3.6.1.4.1.8072.3.1"
], [
"name" => "netSnmpAgentOIDs",
"oid" => "1.3.6.1.4.1.8072.3.2"
], [
"name" => "netSnmpDomains",
"oid" => "1.3.6.1.4.1.8072.3.3"
], [
"name" => "netSnmpNotificationPrefix",
"oid" => "1.3.6.1.4.1.8072.4"
], [
"name" => "netSnmpNotifications",
"oid" => "1.3.6.1.4.1.8072.4.0"
], [
"name" => "netSnmpNotificationObjects",
"oid" => "1.3.6.1.4.1.8072.4.1"
]
];
$result = makeTree($arr);
print_r($result);
I need to take an array that turns the key (split by /) of each element into a child array, and assigns the data in the right format in the new array.
There can be multiple levels of nesting, realistically never more then 10, but that is to be decided.
For example;
given the input of
$i_have_this = [
"Base/child" => [
[
"filename" => "child-1",
"last_modified" => "29/01/2020"
],
[
"filename" => "child-2",
"last_modified" => "29/01/2020"
],
[
"filename" => "child-3",
"last_modified" => "29/01/2020"
]
],
"Base/child/grandChild1" => [
[
"filename" => "grandChild1-1",
"last_modified" => "29/01/2020"
]
],
"Base/child/grandChild2" => [
[
"filename" => "grandChild2-1",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-2",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-3",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-4",
"last_modified" => "29/01/2020"
],
[
"filename" => "grandChild2-5",
"last_modified" => "29/01/2020"
]
]
];
I would like the output of
$want_this = [
'name' => 'Base',
'children' => [
[
'name' => 'child',
'children' => [
["name" => "child-1"],
["name" => "child-2"],
["name" => "child-3"],
[
"name" => "grandChild1",
"children" => [
["name" => "grandChild1-1"]
]
],
[
"name" => "grandChild2",
"children" => [
["name" => "grandChild2-1"],
["name" => "grandChild2-2"],
["name" => "grandChild2-3"],
["name" => "grandChild2-4"]
]
],
]
]
]
];
So far I have;
foreach($i_have_this as $path => $value) {
$temp = &$want_this;
foreach (explode('/', $path) as $key) {
$temp = &$temp[$key];
}
$temp = $value;
}
but can't quite finish it off.
Example code run here
I think you could treat this the same way that most use "dot" notation for arrays (like in Laravel). Just replace "." with "/" in your case:
Example Code
function unflatten($data) {
$output = [];
foreach ($data as $key => $value) {
$parts = explode('/', $key);
$nested = &$output;
while (count($parts) > 1) {
$nested = &$nested[array_shift($parts)];
if (!is_array($nested)) $nested = [];
}
$nested[array_shift($parts)] = $value;
}
return $output;
}
print_r(unflatten($i_have_this));
I am currently able to sort a multidimensional array using a custom sorting method. Each array lineupSet has an n amount of items. The function sort_points will sort each lineupSet from highest to lowest totalPoints and then it will give me the lineupSet with the the highest total totalPoints. I am currently changing the approach, I still want to sort through each lineupSet first and order highest to lowest. Then I would like to get the highest totalPoints of each lineupSet based on a given count. What would be the best way to approach this?
Test Array:
$testArray = [[
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 214.61,
],
"name" => "arr0-test0",
], [
"formula" => [
"totalPoints" => 201.17,
],
"name" => "arr0-test1",
]], [
"formula" => [
"totalPoints" => 5.01,
],
"name" => "arr0-test2",
]],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 214.76,
],
"name" => "arr1-test0",
], [
"formula" => [
"totalPoints" => 220.66,
],
"name" => "arr1-test1",
]],
],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 205.71,
],
"name" => "arr2-test0",
], [
"formula" => [
"totalPoints" => 204.43,
],
"name" => "arr2-test1",
]],
],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 205.48,
],
"name" => "arr3-test0",
], [
"formula" => [
"totalPoints" => 203.51,
],
"name" => "arr3-test1",
]],
],
]];
Sorting Function
function sum_points($v) {
$totalPoints = 0;
foreach ($v['lineupSet'] as $lset) {
if (isset($lset['formula'])) {
$totalPoints += $lset['formula']['totalPoints'];
}
else {
foreach ($lset as $l) {
$totalPoints += $l['formula']['totalPoints'];
}
}
}
return $totalPoints;
}
function sort_points($a, $b) {
return sum_points($b) - sum_points($a);
}
usort($testArray, 'sort_points');
print_r($testArray[0]);
For example I want to get the top two highest 'totalPoints'. The desired outcome:
Array (
[lineupSet] => Array
(
[0] => Array
(
[0] => Array
(
[formula] => Array
(
[totalPoints] => 220.66
)
[name] => arr1-test1
)
[1] => Array
(
[formula] => Array
(
[totalPoints] => 214.76
)
[name] => arr0-test0
)
)
)
)
I want to do the same for the top n highest totalPoints. Keeping in mind that it will have to take at times n items from each lineupSet that are the highest totalPoints.
I think it's better to use an object then you can keep max while you are sorting data (also you can use a constructor to sort the array).
Class SortHelper{
public $max = 0;
private function checkMax($totalPoints){
if($totalPoints > $this->max)
$this->max = $totalPoints;
}
private function sum_points($v) {
$totalPoints = 0;
foreach ($v['lineupSet'] as $lset) {
if (isset($lset['formula'])) {
$totalPoints += $lset['formula']['totalPoints'];
$this->checkMax($lset['formula']['totalPoints']);
}
else {
foreach ($lset as $l) {
$totalPoints += $l['formula']['totalPoints'];
$this->checkMax($l['formula']['totalPoints']);
}
}
}
return $totalPoints;
}
private function sort_points($a, $b) {
return $this->sum_points($b) - $this->sum_points($a);
}
public function sort($array){
usort( $array, [$this, 'sort_points']);
return $array;
}
}
then you would have:
$sortHelper = new SortHelper();
$sorted_array = $sortHelper->sort($testArray);
var_dump($sorted_array[0]);
var_dump($sortHelper->max);
Check this out,
$n = 2; // number of elements
$testArray = [[
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 214.61,
],
"name" => "arr0-test0",
], [
"formula" => [
"totalPoints" => 201.17,
],
"name" => "arr0-test1",
]], [
"formula" => [
"totalPoints" => 5.01,
],
"name" => "arr0-test2",
]
],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 214.76,
],
"name" => "arr1-test0",
], [
"formula" => [
"totalPoints" => 220.66,
],
"name" => "arr1-test1",
]],
],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 205.71,
],
"name" => "arr2-test0",
], [
"formula" => [
"totalPoints" => 204.43,
],
"name" => "arr2-test1",
]],
],
], [
"lineupSet" => [
[[
"formula" => [
"totalPoints" => 205.48,
],
"name" => "arr3-test0",
], [
"formula" => [
"totalPoints" => 203.51,
],
"name" => "arr3-test1",
]],
],
]];
function sort_points($a, $b)
{
return ($a['formula']['totalPoints'] > $b['formula']['totalPoints']) ? -1 : 1;
}
$result = [];
$reference = &$result['lineupSet'][0]; // store reference in $reference
foreach ($testArray as $tA) {
foreach ($tA['lineupSet'] as $t) {
foreach ($t as $child) {
$reference[] = $child;
}
}
}
usort($reference, 'sort_points');
$reference = array_slice($reference, 0, $n);
var_dump($result); // desired output
Please try this 2 functions, the steps as follows:
sort_points(); Sort it with arsort PHP function and return it to new simplified array.
transform_arrays($sortedArray, $testArray); $testArray will be changed the value by the sortedArray.
copy all the codes into single file of .php and run it.
*i assume that we don't care about the complexity, also in real life (in this case my life) there is nothing such the case that you need to sort in multidimensional array, because array is just the storage to keep the data can be read by human.
<?php
$testArray =
[
['lineupSet' => [[['formula' => ['totalPoints' => 214.61],'name' => 'arr0-test0'],['formula' => ['totalPoints' => 220.66],'name' => 'arr1-test1']]]],
['lineupSet' => [[['formula' => ['totalPoints' => 205.71],'name' => 'arr2-test0'],['formula' => ['totalPoints' => 204.43],'name' => 'arr2-test1']]]],
['lineupSet' => [[['formula' => ['totalPoints' => 205.48],'name' => 'arr3-test0'],['formula' => ['totalPoints' => 203.51],'name' => 'arr3-test1']]]]
];
// sort into another array
function sort_points($testArray = []){
$response = $result = [];
$i = 0;
foreach($testArray as $array2){
foreach($array2['lineupSet'][0] as $item){
$result[$item['name']] = $item['formula']['totalPoints'];
$i++;
}
}
arsort($result);
$i = 0;
foreach($result as $key => $val){
$response[$i]['name'] = $key;
$response[$i]['totalPoints'] = $val;
$i++;
}
return $response;
}
// this won't work if the $testArray format structure is changed
function transform_arrays($items, $testArray){
$l = 0;
for($i=0;$i<count($testArray);$i++){
for($j=0;$j<count($testArray[$i]);$j++){
for($k=0;$k<count($testArray[$i]['lineupSet'][$j]);$k++){
$testArray[$i]['lineupSet'][$j][$k]['formula']['totalPoints'] = $items[$l]['totalPoints'];
$testArray[$i]['lineupSet'][$j][$k]['name'] = $items[$l]['name'];
// print_r($testArray[$i]['lineupSet'][$j][$k]['formula']['totalPoints']);
// print_r($testArray[$i]['lineupSet'][$j][$k]);
$l++;
}
}
}
return $testArray;
}
echo '<pre>';
$sortedArray = sort_points($testArray);
$response = transform_arrays($sortedArray, $testArray);
print_r($response);
echo '</pre>';