php make nested array from multiple arrays - php

I want to make a nicely structured category with parent tree, but i can't seem to find the right way to get do this properly.
This is the premis:
1 product has many categories.
1 category may have a parent.
this is the array I currently have (the values in these arrays are just an example)
array:4[
1=> array:2 [
2 =>2
4 => 4
]
2=> array:1[
3=>3
]
3=> array:1[
5=>5
]
9=> array:1[
6=>6
]
]
now i want this
array:4[
1=> array:2 [
2 => array:1 [
3=> array:1 [
5=>5
]
]
4 => 4
]
9=> array:1[
6=>6
]
]
how do I convert the first to the second while keeping the array keys, I want to use the keys to call upon an another array with all the relevant objects and data
so i can get this as a result:
http://www.bootply.com/dFeKoQmgb2

What if you try the following code. Note that there must be a better approach without changing the source array inside recursion.
$array = array(
1 => array(
2 => 2,
4 => 4
),
2 => array(
3 => 3
),
3 => array(
5 => 5
),
9 => array(
6 => 6
)
);
//we need the copy to delete there nodes that have been re-atached. Otherwise, we end up changing the array itself within recursion, which is not good
$arrayCopy = $array;
function recursive(&$array, &$arrayCopy) {
foreach($array as $key => &$value) {
//if we found the leaf node
if(!is_array($value)) {
// and there is something in the array copy with this index
if(array_key_exists($key, $arrayCopy)) {
// re-attach it to the initial array. $value is passed by reference. BTW, not a good approach as I stated above, but it works on the sample data
$value = $arrayCopy[$key];
// remove that node from array copy in order to not double process it.
unset($arrayCopy[$key]);
// call it again for the leaf-node value
recursive($value, $arrayCopy);
}
// here is recursion exit, if we don't find anything in the arracy copy for the fiven leaf node
} else {
//recursive call it for the node
recursive($value, $arrayCopy);
}
}
}
// launch the recursion
recursive($array, $arrayCopy);
// now we need to remove the nodes that have been removed from the array copy, i.e. the ones being re-attached. We could not do this in the recursion
$result = array();
foreach(array_keys($arrayCopy) as $initial_key) {
$result[$initial_key] = $array[$initial_key];
}
var_dump($result);

for now I fixed it using this function, I'm sure there is a more elegant way, since it only works when the parent is also in the array, for this but it works
array (size=5)
0 =>
array (size=3)
'id' => int 2
'parent' => int 1
'name' => string 'voluptatem' (length=10)
1 =>
array (size=3)
'id' => int 3
'parent' => int 2
'name' => string 'qui' (length=3)
2 =>
array (size=3)
'id' => int 4
'parent' => int 1
'name' => string 'qui' (length=3)
3 =>
array (size=3)
'id' => int 5
'parent' => int 3
'name' => string 'voluptate' (length=9)
4 =>
array (size=3)
'id' => int 6
'parent' => int 9
'name' => string 'optio' (length=5)
private function generatePageTree($datas, $parent=0, $limit=0)
{
if ($limit > 100) return '';
$tree='<ul class="list-group">';
foreach ($datas as $data) {
if ($data['parent'] == $parent) {
$tree.='<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="false">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading' . $data['id'] . '">
<h4 class="panel-title" style="height:50px;">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="accordion" href="#collapse' . $data['id'] . '" aria-expanded="false" aria-controls="collapse' . $data['id'] . '">
' . $data['name'] . '
</a>
<i class="fa fa-search"></i>Toon
</h4>
</div>
<div id="collapse' . $data['id'] . '" class="panel-collapse collapse out" role="tabpanel" aria-labelledby="heading' . $data['id'] . '">
<div class="panel-body">';
$tree.=$this->generatePageTree($datas, $data['id'], $limit ++);
$tree.='</div>
</div>
</div>
</div>
';
}
}
$tree.='</ul>';
return $tree;
}

Related

PHP Recursive Loop using UASORT on Multidimensional Array

I am writing a script that loops through a multidimensional array and it's working as hoped (sort of) but I get errors that I just can't remedy.
I am still not that comfortable building loops to manage nested arrays.
Here is my code. The goal is to sort each layer by the value of the sequence key and in the end I export the array as json.
The sequence key may or may not exist in every sub array so that may need some sort of if clause
<?php
$list = [
"key" => "book",
"sequence" => 1,
"items" => [
[
"key" => "verse",
"sequence" => 2,
"items" => [
["sequence" => 3],
["sequence" => 1],
["sequence" => 2],
],
],
[
"key" => "page",
"sequence" => 1,
"items" => [
[
"key" => "page",
"sequence" => 2,
"items" => [
["sequence" => 2],
["sequence" => 1],
["sequence" => 3],
],
],
[
"key" => "paragraph",
"sequence" => 1,
"items" => [
["sequence" => 2],
["sequence" => 1],
["sequence" => 3],
],
],
],
],
],
];
function sortit(&$array){
foreach($array as $key => &$value){
//If $value is an array.
if(is_array($value)){
if($key == "items"){
uasort($value, function($a,&$b) {
return $a["sequence"] <=> $b["sequence"];
});
}
//We need to loop through it.
sortit($value);
} else{
//It is not an array, so print it out.
echo $key . " : " . $value . "<br/>";
}
}
}
sortit($list);
echo "<pre>";
print_r($list);
?>
Here is the output and error I am getting, and I think I understand why the error is being thrown but at the same time I can not implement the proper checks needed to fix the error.
key : book
sequence : 1
key : page
sequence : 1
E_WARNING : type 2 -- Illegal string offset 'sequence' -- at line 39
E_NOTICE : type 8 -- Undefined index: sequence -- at line 39
sequence : 1
sequence : 2
sequence : 3
sequence : 1
key : page
E_WARNING : type 2 -- Illegal string offset 'sequence' -- at line 39
E_NOTICE : type 8 -- Undefined index: sequence -- at line 39
sequence : 1
sequence : 2
sequence : 3
sequence : 2
key : verse
Not that I am worried to much but another thing that I would like is the array to still be structured in the original order, ie: key, sequence, items
Using usort and array references makes it straightforward. If we're dealing with an array with a set item key, sort the item array and recurse on its children, otherwise, we're at a leaf node and can return.
function seqSort(&$arr) {
if (is_array($arr) && array_key_exists("items", $arr)) {
usort($arr["items"], function ($a, $b) {
return $a["sequence"] - $b["sequence"];
});
foreach ($arr["items"] as &$item) {
$item = seqSort($item);
}
}
return $arr;
}
Result:
array (
'key' => 'book',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'key' => 'page',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'key' => 'page',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'sequence' => 1,
),
1 =>
array (
'sequence' => 2,
),
2 =>
array (
'sequence' => 3,
),
),
),
),
),
1 =>
array (
'key' => 'verse',
'sequence' => 2,
'items' =>
array (
0 =>
array (
'sequence' => 1,
),
1 =>
array (
'sequence' => 2,
),
2 =>
array (
'sequence' => 3,
),
),
),
),
)
Try it!
Note that the outermost structure is a root node that isn't part of an array and can't be sorted (this may be unintentional and causing confusion).

Array sort menu

I have the following array to show menu's based on the order the user specified.
The array is as follows:
$menuArray = [
'Main Street' => [
['/index.php', 'Home'],
['/city.php', $cityData[$user->city][0]],
['/travel.php', 'Travel'],
['/bank.php', 'Bank'],
['/inventory.php', 'Inventory'],
['/dailies.php', 'Dailies'],
],
'Activities' => [
(!$my->hospital) ? ['/hospital.php', 'Hospital'] : [],
(!$my->hospital && !$my->prison) ? ['/crime.php', 'Crime'] : [],
['/missions.php', 'Missions'],
['/achievements.php', 'Achievements'],
],
'Services' => [
['/hospital.php', 'Hospital'],
['/prison.php', 'Prison'],
['/search.php', 'Search'],
],
'Account' => [
['/edit_account.php', 'Edit Account'],
['/notepad.php', 'Notepad'],
['/logout.php', 'Logout'],
]
];
I have a column menu_order stored in the database, which has a default value of 0,1,2,3,4, but this can change per user as they will be able to change their menu to their likes.
What I'd like to achieve:
0 => Main Street
1 => Activities
2 => Services
3 => Account
4 => Communication
To get the menu order, I do
$menuOrder = explode(',', $user->menu_order);
But I'm not sure how to handle the foreach for displaying the menu.
Here's one way to do it -- use replacement rather than a sorting algorithm.
Code: (Demo)
$menuArray = [
'Main Street' => [],
'Activities' => [],
'Services' => [],
'Account' => []
];
$lookup = [
0 => 'Main Street',
1 => 'Activities',
2 => 'Services',
3 => 'Account',
4 => 'Communication'
];
$customsort = '4,2,1,3,0';
$keys = array_flip(explode(',', $customsort)); convert string to keyed array
//var_export($keys);
$ordered_keys = array_flip(array_replace($keys, $lookup)); // apply $lookup values to keys, then invert key-value relationship
//var_export($ordered_keys);
$filtered_keys = array_intersect_key($ordered_keys, $menuArray); // remove items not on the current menu ('Communication" in this case)
//var_export($filtered_keys);
$final = array_replace($filtered_keys, $menuArray); // apply menu data to ordered&filtered keys
var_export($final);
Output:
array (
'Services' =>
array (
),
'Activities' =>
array (
),
'Account' =>
array (
),
'Main Street' =>
array (
),
)
And here's another way using uksort() and a spaceship operator:
$ordered_keys = array_flip(array_values(array_replace(array_flip(explode(',', $customsort)), $lookup)));
uksort($menuArray, function($a, $b) use ($ordered_keys) {
return $ordered_keys[$a] <=> $ordered_keys[$b];
});
var_export($menuArray);
As a consequence of how your are storing your custom sort order, most of the code involved is merely to set up the "map"/"lookup" data.
You could try something like this to produce the menu:
function display_menu($menus, $m) {
if (!isset($menus[$m])) return;
echo "<ul>";
foreach ($menus[$m] as $item) {
if (!count($item)) continue;
echo "<li>{$item[1]}\n";
}
echo "</ul>";
}
$menuMap = array(0 => 'Main Street',
1 => 'Activities',
2 => 'Services',
3 => 'Account',
4 => 'Communication');
$menuOrder = explode(',', $user->menu_order);
foreach ($menuOrder as $menuIndex) {
$thisMenu = $menuMap[$menuIndex];
display_menu($menuArray, $thisMenu);
}
Small demo on 3v4l.org

how to set selected multiple combo box based on array

I have two sets of array ,first array contains all categories called "all", and second array contains selected categories called "selected", I want to populate this concept to multiple combo box,
$all = [
0 => [
'id'=>1,
'name' => 'news'
],
1 => [
'id'=>2,
'name' => 'tips'
],
2 => [
'id'=>3,
'name' => 'trick'
],
3 => [
'id'=>4,
'name' => 'review'
]
];
$selected = [
0 => [
'id'=>2,
'name' => 'trick'
],
1 => [
'id'=>4,
'name' => 'review'
],
];
I've try to do foreach in foreach , but i have duplicated data when show in combo box, i want to have all data from "all" shown with selected data from "selected".
i just solved my problem in deferent way , first i add default pair of key and value "sel"=>0 in "all" array set, then i loop trough array "all" and array "sel" to get similar value and when it match change sel key to 1 ,this code for further explanation
public static function compare($sel,$all){
// add sel key with default value = 0
foreach($all as $k=>$v){
$all[$k]['sel'] = 0;
}
foreach($all as $k=>$v){
foreach($sel as $k2=>$v2){
// when match change sel to 1
if($v['id'] == $v2['id']){
$all[$k]['sel'] = 1;
}
}
}
return $all;
}
final result :
$all = [
0 => [
'id'=>1,
'name' => 'news',
'sel' => 0
],
1 => [
'id'=>2,
'name' => 'tips',
'sel' => 0
],
2 => [
'id'=>3,
'name' => 'trick',
'sel' => 1
],
3 => [
'id'=>4,
'name' => 'review',
'sel' => 1
]
];
just add if condition when $all['sel'] = 1 they should be selected, thanks all :D
You can get the intersection of both arrays with array_uintersect and a custom callback function (compare).
function compare($a, $b){
if($a['id'] == $b['id']){
return 0;
}
return 1;
}
$res = array_uintersect($selected, $all,"compare");
print_r($res);
>Array ( [0] => Array ( [id] => 2 [name] => trick ) [1] => Array ( [id] => 4 [name] => review ) )
After that you only need to loop through the final array and set the corresponding check boxes.
If you want to compare by name just create another callback function.
function compare2($a, $b){
if($a['name'] == $b['name']){
return 0;
}
return 1;
}
The duplicates are caused by the inner for loop continuing to create select elements even after it has found a selected element. You can avoid having an inner loop and using php's in_array() function to check if $all is in $selected like this:
$x = '';
foreach($all as $a){
if(in_array($a, $selected)){
$x .= '<option selected>'.$a['id'].'Selected </option>';
}else{
$x .= '<option>'.$a['id'].'Not selected </option>';
}
}
echo $x;
Note that in_array will check all values of the elements, so for example element with id 2 but different name will appear as not selected. You may want to change both names to tips. I hope that helps.

Transpose an array of arrays [duplicate]

This question already has answers here:
Transposing multidimensional arrays in PHP
(12 answers)
Closed 6 months ago.
Hah, I had no idea how else to phrase that. I'm trying to reformat a set of three arrays generated by form field inputs, into something that better matches my models, so I can save the values to the db.
Not sure if the solution should be some array manipulation or that I should change the "name" attribute in my form fields.
currently I have an array of my input data:
array(
'image_id' =>
array
0 => '454' (length=3),
1 => '455' (length=3),
2 => '456' (length=3)
'title' =>
array
0 => 'title1' (length=6),
1 => 'title2' (length=0),
2 => '' (length=6)
'caption' =>
array
0 => 'caption1' (length=8),
1 => '' (length=8),
2 => 'caption3' (length=8)
);
and would like to change it to something like, so I can iterate over and save each array of values to the corresponding resource in my db.
array(
0 =>
array
'image_id' => '454',
'title' => 'title1',
'caption' => 'caption1'
1 =>
array
'image_id' => '455',
'title' => 'title2',
'caption' => ''
2 =>
array
'image_id' => '456',
'title' => '',
'caption' => 'caption3'
);
This would iterate through the array with 2 foreach loops. They would use each other's key to construct the new array, so it would work in any case:
$data = array(
'image_id' => array(454, 455, 456),
'title' => array('title1', 'title2', ''),
'caption' => array('caption1', '', 'caption3')
);
$result = array();
foreach($data as $key => $value) {
foreach ($value as $k => $v) {
$result[$k][$key] = $v;
}
}
This'll do it:
$array = call_user_func_array('array_map', array_merge(
[function () use ($array) { return array_combine(array_keys($array), func_get_args()); }],
$array
));
Assuming though that this data is originally coming from an HTML form, you can fix the data right there already:
<input name="data[0][image_id]">
<input name="data[0][title]">
<input name="data[0][caption]">
<input name="data[1][image_id]">
<input name="data[1][title]">
<input name="data[1][caption]">
Then it will get to your server in the correct format already.

Retrieve Child Objects

I want a table of comments like so
id | comment | parent_id
--------------------------
1 text1 0
2 text2 1
3 text3 2
4 text4 3
5 text5 3
6 text6 5
I want to construct an array displaying the hierarchy of the parents and children. The tree should go back a undetermined number of generations. I don't want to use nesting foreach loops as I'm not sure how deep it goes. That is why I'm here, I'm not sure of the best practice for a problem like this. I also want to display the depth in the array. Below is an example. It doesn't really relate to table above, but hopefully gives you an idea of what I need.
array(
"depth"=> 4
"parent" => array(
"id"=> 1,
"comment" => "sometext1"
"child_count" => 2,
"children" => array(
0 => array(
"id" => 2
"comment" => "sometext2",
"child_count" => 0,
"children" => null
),
1 => array(
"id" => 3
"comment" => "sometext3"
"child_count" => 1,
"children" => array(
0 => array(
"id" => 2
"comment" => "sometext2",
"child_count" => 2,
"children" => array(
0 => array(
"id" => 2
"comment" => "sometext2",
"child_count" => 0,
"children" => null
),
1 => array(
"id" => 2
"comment" => "sometext2",
"child_count" => 1,
"children" => array(
"id" => 2
"comment" => "sometext2",
"child_count" => 0,
"children" => null
)
)
)
)
)
)
)
)
I was going to use foreach and do a SQL statement to retrive that parent/childs children. ie
$sql = "SELECT * FROM comments WHERE parent = $parent_id";
Im not really looking for the code for all this, just a pseudo code solution.
This can be easily done in PHP... For this you need two arrays and a two while loops.
This code will make a tree the way you wanted and for an undetermined depth and number of children.
Pastebin to the working code.
Using references, lets imagine everything is saved in an array $data with this structure: (id, comment, parent_id) where parent_id points to an id.
Code to build the tree.
$tree = array();
reset($data);
while (list($k, $v) = each($data))
if (0 == ($pid = $v['parent_id']))
$tree[$k] =& $data[$k]; else
$data[$pid]['children'][$k] =& $data[$k];
And to generate the depth and child count.
reset($data);
while (list($k, $v) = each($data))
if (0 != $v['parent_id'])
{
$ref =& $data[$k];
$depth = 0;
do
{
if ($depth) $ref =& $data[$ref['parent_id']];
$dre =& $ref['depth'];
if (!isset($dre) || $dre <= $depth) $dre = $depth++;
if (isset($ref['children']))
$ref['child_count'] = count($ref['children']);
else
{
$ref['child_count'] = 0;
$ref['children'] = null;
}
}
while ($ref['parent_id']);
}
All my code has been written on the fly and not even tested, so if there are any errors please forgive meeeeeeeee!!!!!!!!!!! ← Forget that, I tried it, fixed a couple of issues and now works perfectly.
Note
For this code to work, the index of every item has to be equal to its ID.
The array I used to try the code.
$data = array(
'1' => array('id' => '1', 'comment' => 'a', 'parent_id' => 0),
'2' => array('id' => '2', 'comment' => 'b', 'parent_id' => 0),
'3' => array('id' => '3', 'comment' => 'c', 'parent_id' => 1),
'4' => array('id' => '4', 'comment' => 'd', 'parent_id' => 1),
'5' => array('id' => '5', 'comment' => 'e', 'parent_id' => 2),
'6' => array('id' => '6', 'comment' => 'f', 'parent_id' => 2),
'7' => array('id' => '7', 'comment' => 'g', 'parent_id' => 5),
'8' => array('id' => '8', 'comment' => 'h', 'parent_id' => 7)
);
This is the problem when you use Adjacency list for trying to retrieve all child nodes in the hierarchy. It just doesn'y handle recursion very well if you are using mysql. (Oracle is another matter).
Creating the structure is simple, you should not really concern yourself with how to create the array structure just yet, first you want to try and create an efficient query and effiecient models that play perfectly to the type of queries that you will be making.
For example, you say that you want to retrieve all child nodes. Well then you should probably be using nested set models instead or in addition to adjacency list.
Take a look at some of these resources...
Is there a simple way to query the children of a node?
The idea of a nested set, is that you store the lft and right edge values of a node, meaning that retrieving any child nodes, is incredibly simple, beause you just select nodes which have a lft value greater than the target nodes lft value, and smaller than the rgt value.
Once you retrieve your result set, creating your array structure will be effortless.
See here : http://en.wikipedia.org/wiki/Nested_set_model
Once you have your results, then take a look at this question, which I asked a year or so ago, which is exactly what you want. PHP > Form a multi-dimensional array from a nested set model flat array
Example
id | comment | parent_id | lft | rgt |
-------------------------------------------------
1 World null 1 12
2 Europe 1 2 11
3 England 2 3 10
4 Kent 3 4 5
5 Devon 3 6 9
6 Plymouth 5 7 8

Categories