Hallöschen everyone,
i'm having a hierarchical array which i'm getting from jsTree when I copy+paste a node and I want to insert the new tree branch into database.
My table has an adjacency list structure with the three fields 'id','text','parent_id'.
The structure can be like below or more nested. I did't figured out how to keep track of the parent so to know which children belong to which parent as I walk the array recursively.
What could be the cleanest approach?
Array(
[id] => 1
[text] => 'a'
[children]
=>Array(
[0] => Array
(
[id] => 2
[text] => 'b'
[children] => []
)
[1] => Array
(
[id] => 3
[text] => 'c'
[children] => Array(
[0] => Array(
[id] => 4
[text] =>''
[children]=>[]
)
)
)
)
)
So the final sollution that works for me is this one
function insertRows($data, $parent_id)
{
foreach ($data as $key => $value) {
if(isset($value['text']))
// here insert the the $value['text'] and get the generated $id
if (isset($value['children'])) {
insertRows($value['children'], $id);
}
}
}
To duplicate the dataset try this:
function insertRows($array, $parentId = null)
{
/** #var PDO $pdo */
global $pdo;
foreach ($array as $key => $value) {
// Add text and parent id to table.
$pdo->query('INSERT INTO table SET text = "' . $value['text'] . '", parent_id = ' . $parentId);
if (is_array($value['children'])) {
insertRows($value['children'], $pdo->lastInsertId());
}
}
}
Its untested an similar to pseudocode. Its more a hint.
You can have third index as parent_id and for root element make parent_id as 0 and in thier respective children put parent_id as id of parent.
for root element :
Array(
[id] => 1,
[text] => 'a',
[parent_id] => 0
);
for 1st child it would be like
Array(
[id] => 2,
[text] => 'a',
[parent_id] => 1
);
Related
I try to sort an array by name. I want to create a structure similar to a menu. First, I allow a function to write values into the array. Then I would like to assign all subpages to the parent (resulting from the structure, such as a URL).
Out
array
(
[0] => array
(
[Pages] => coreViewSites
)
[1] => array
(
[Pages / Create] => create coreView
)
[2] => array
(
[Pages / Duplicate] => coreViewSites
)
[3] => array
(
[Pages / Anarchy] => coreViewSites
)
[4] => array
(
[User] => coreViewUser
)
)
should an array like
array
(
[Pages] => Array
(
[0] => ABC
[Create] => ABC
[Duplicate] => ABC
[Anarchy] => ABC
)
[User] => ABC
)
become.
Do you have an idea how I could solve this?
Assuming the menu items are ordered with parents coming before children, here is how you could do that:
$menu = [];
foreach($input as $path) {
$keys = explode(" / ", key($path)); // Extract the individual menu titles
$last = array_pop($keys); // Pull the last one from it
$loc = &$menu;
foreach($keys as $key) {
// Create menu item if it does not exist yet
if (!isset($loc[$key])) $loc[$key] = [];
// When a menu gets sub-menu, move the title in index 0 of a new array
if (!is_array($loc[$key])) $loc[$key] = [$loc[$key]];
$loc = &$loc[$key]; // Set the pointer to that submenu
}
$loc[$last] = reset($path); // At the deepest level assign the menu title.
}
// Print result:
var_export($menu);
Output:
array (
'Pages' => array (
0 => 'coreViewSites',
'Create' => 'create coreView',
'Duplicate' => 'coreViewSites',
'Anarchy' => 'coreViewSites',
),
'User' => 'coreViewUser',
)
I've got a database table that looks like this:
uid | group | category
1 | group1 | cat1
2 | group1 | cat2
3 | group2 | cat3
4 | group2 | cat4
5 | group2 | cat5
6 | group3 | cat6
7 | group3 | cat7
But I need this data in an array, that groups categories by their group.
For example, my array should look like this:
Array
(
[group1] => Array
(
[0] => Array
(
[0] => 1
[1] => cat1
)
[1] => Array
(
[0] => 2
[1] => cat2
)
)
[group2] => Array
(
[0] => Array
(
[0] => 3
[1] => cat3
)
[1] => Array
(
[0] => 4
[1] => cat4
)
[2] => Array
(
[0] => 5
[1] => cat5
)
)
[group3] => Array
(
[0] => Array
(
[0] => 6
[1] => cat6
)
[1] => Array
(
[0] => 7
[1] => cat7
)
)
)
I've written a foreach loop that does just this, but I have a problem.
My problem is that it always leaves out the very last row of the table, and I'm not sure how to fix it. In my mind, the logic dictates that it should always work.
I was thinking that after the loop I could just add the very last row to the new array, but I think that may cause issues if the last row has a different group, and I would rather the solution be built into the foreach loop.
Unfortunately, I am at a loss here. How can I fix my code to include the very last row of the database query?
I would also be interested to see what improvements I can make on my current code, but that may be a better question for codereview.
My loop:
$pass = [];
foreach($stmt as $key => $value) {
if(empty($currentGroup)) $currentGroup = $value['group'];
if(empty($temp)) $temp = [];
if($currentGroup != $value['group'] || $key+1 == count($stmt)) {
$pass[$currentGroup] = $temp;
$currentGroup = $value['group'];
$temp = [];
$temp[] = [$stmt[$key]['uid'], $stmt[$key]['category']];
} else {
$temp[] = [$stmt[$key]['uid'], $stmt[$key]['category']];
}
}
The following should do it:
<?php
//Create an array to store our grouped rows
$grouped = array();
//Loop over all rows returned by the $stmt that has been executed.
//You could probably remove the key from here, it's not needed it seems.
//The keys within the $value array will match the names of the columns in
//the database,
foreach($stmt as $key => $value){
//As we're storing by the group value from the row we first want to
//check if our grouped array contains a key for the group of the row
//being processed. If it does not, create an empty array within the
//grouped data for this group.
if(!array_key_exists($value['group'], $grouped)){
$grouped[$value['group']] = array();
}
//Knowing we will always have an array element for the rows group
//we can blindly append the values for this row to the grouped
//container using its values.
//'[] =' is just short hand append.
$grouped[$value['group']][] = array(
$value['uid'],
$value['category']
);
}
Hope that helps!
To further future proof this loop you could change the grouped value append to the following:
<?php
//Setting the whole row (minus the group) rather than just the uid
//and category explicitly allows this code to work without modification
//as the datatable changes, ie. new columns. Assuming that is the 'group'
//column remains present
unset($value['group']);
$grouped[$value['group']][] = $value;
Grouped contents data could now be accessed using the following:
<?php
//Acceess data via column name not array index, yay!
echo $grouped['group1']['uid']
I've needed this again recently, so I made a function based around #JParkinson1991's answer.
I'm putting this here for documentation, and to possibly help future readers.
function groupArray($arr, $group, $preserveSubArrays = false, $preserveGroupKey = false) {
$temp = array();
foreach($arr as $key => $value) {
$groupValue = $value[$group];
if(!$preserveGroupKey)
{
unset($arr[$key][$group]);
}
if(!array_key_exists($groupValue, $temp)) {
$temp[$groupValue] = array();
}
if(!$preserveSubArrays){
$data = count($arr[$key]) == 1? array_pop($arr[$key]) : $arr[$key];
} else {
$data = $arr[$key];
}
$temp[$groupValue][] = $data;
}
return $temp;
}
Breakdown
function groupArray($arr, $group, $preserveGroupKey = false, $preserveSubArrays = false)
This function accepts 2 to 4 parameters.
The flat array you want to group (array)
The key you want to group by (string/int)
Option to preserve the group key in the output of each sub array (Boolean)
Option to preserve sub arrays. If only 1 key exists in each sub array, the function will store just the single value for each row instead of an array (Boolean)
The first parameter is the array itself, the second parameter is the key that you want to group by, and the 3rd (optional) parameter is a boolean that tells the function if you want to preserve the group key in the sub arrays.
$temp = array();
foreach($arr as $key => $value) {
$groupValue = $value[$group];
if(!$preserveGroupKey)
{
unset($arr[$key][$group]);
}
if(!array_key_exists($groupValue, $temp)) {
$temp[$groupValue] = array();
}
$temp[$groupValue][] = $arr[$key];
}
First, we create a temporary array called $temp
Next, we loop through the array grabbing the key (Which should be a string or int), and the value (Which should be an array).
We set $groupValue to whatever the value is of the $group you chose, such as "group" in the example below.
$arr = [
0 => [
"group" => "group1",
"name" => "Bob",
],
1 => [
"group" => "group1",
"name" => "Randy",
],
2 => [
"group" => "group1",
"name" => "Susan",
],
3 => [
"group" => "group2",
"name" => "Larry",
],
4 => [
"group" => "group2",
"name" => "David",
],
5 => [
"group" => "group3",
"name" => "Perry",
],
];
Then we check if we want to $preserveGroupKey's. If this boolean is false (And it is by default), the key will be removed leaving several subarrays with just the "name" key left.
Now we check if the $groupValue exists in our $temp array, if it does not, we create it.
Then we add to the $temp[$groupValue] whatever the current row values are. From the example above, we would end up with:
Array
(
[group1] => Array
(
[0] => Bob
[1] => Randy
[2] => Susan
)
[group2] => Array
(
[0] => Larry
[1] => David
)
[group3] => Array
(
[0] => Perry
)
)
Or, with the 3rd parameter set to true you would get:
Array
(
[group1] => Array
(
[0] => Array
(
[name] => Bob
)
[1] => Array
(
[name] => Randy
)
[2] => Array
(
[name] => Susan
)
)
[group2] => Array
(
[0] => Array
(
[name] => Larry
)
[1] => Array
(
[name] => David
)
)
[group3] => Array
(
[0] => Array
(
[name] => Perry
)
)
)
I would like to create a category-tree-array, with unlimited subcategories. Without having to nest foreach loops in foreach loops.
The goal is to have an array of categories in the right order, that I can use for multiple other functions. I need to be able to easily go through this array to find more underlying data. For instance to fetch and later display the (blog, page or product) items that reside in these categories.
It should look something like this:
Array
(
[0] => Array
(
[url] => url_to_cat
[title] => Top Category 1
[depth] => 0
)
[1] => Array
(
[url] => url_to_cat
[title] => Top Category 2
[depth] => 0
)
[2] => Array
(
[url] => url_to_cat
[title] => Sub Category 1 of Category 2
[depth] => 1
)
[3] => Array
(
[url] => url_to_cat
[title] => Sub Category 2 of Category 2
[depth] => 1
)
[4] => Array
(
[url] => url_to_cat
[title] => Sub Category 1 of Sub Category 2
[depth] => 2
)
)
With some help from this and other sites I have come to this function below, wich is in the right direction, but it gets me a multi-dimensional array. That will be difficult for displaying.
The category table has these fields: cat_id, parent_id, title.
The url comes from another table, is not really relevant here.
function category_list($category_parent_id = 0) {
static $cats;
if (!is_array($cats)) {
$sql = "SELECT * FROM category";
$result = $db->query($sql);
while ($record = $result->fetch_array()) {
$cats[] = $record;
}
}
foreach ($cats as $cat) {
// if not a match, move on
if ((int) $cat['parent'] !== (int) $category_parent_id) {
continue;
}
$item[$i]['url'] = 'url';
$item[$i]['title'] = $cat['title'];
$item[$i]['children'] = category_list($cat['cat_id']);
$list_items[] = $item;
}
return $list_items;
}
The initial $cats array:
Array
(
[0] => Array
(
[title] => Top Category 1
[parent] => 0
[cat_id] => 1
)
[1] => Array
(
[title] => Top Category 2
[parent] => 0
[cat_id] => 2
)
[2] => Array
(
[title] => Sub Category 1 of Category 2
[parent] => 2
[cat_id] => 3
)
[3] => Array
(
[title] => Sub Category 2 of Category 2
[parent] => 2
[cat_id] => 4
)
[4] => Array
(
[title] => Sub Sub Category 1 of Sub Category 2
[parent] => 4
[cat_id] => 5
)
[5] => Array
(
[title] => Sub Sub Sub Category 1 of Sub Sub Category 1
[parent] => 5
[cat_id] => 6
)
)
I can not get my head arround how to get the children included in the main array instead of them being a nested array.
I have searched but can not find the right solution for this on here, so I am sorry if it turns out to be a duplicate. Then I would like to get the link to the original question.
I have found it!
The solution was to make the array global that I use to store the list items in.
Also I have added $level to the function, so that I can display a specific class style per depth.
And finally the recursive use of the function is not stored in the array as a nested "children array", but gets passed on to the global array that I return in the end.
This gives me exactly the right array:
function category_list($category_parent_id = 0, $level = 0) {
// build our category list only once
static $cats;
global $list_items
if (!is_array($cats)) {
$sql = "SELECT * FROM category";
$result = $db->query($sql);
while ($record = $result->fetch_array()) {
$cats[] = $record;
}
}
foreach ($cats as $cat) {
// if not a match, move on
if ((int) $cat['parent'] !== (int) $category_parent_id) {
continue;
}
$list_items[] = array (
'title' => $cat['title'],
'id' => $cat['cat_id'],
'level'=> $level
);
category_list($cat['cat_id'], $level + 1);
}
return $list_items;
}
Note: Url is not used in this second test, but that is not relevant for the example.
Pfew. Finally.
I have got this array
Array
(
[0] => Array
(
[category_name] => Dessert1
[totalOrders] => 3
)
[1] => Array
(
[category_name] => Dessert1
[totalOrders] => 1
)
[2] => Array
(
[category_name] => Category 3
[totalOrders] => 1
)
)
and I want to convert it into this array
Array
(
[0] => Array
(
[category_name] => Dessert1
[totalOrders] => 4
)
[1] => Array
(
[category_name] => Category 3
[totalOrders] => 1
)
)
It is really rather simple. You just loop over your data and pick out the unique categories. When there are duplicates add the orders to the category's total.
// The stuff from your post
$data = array(
array('category_name' => 'Dessert1', 'totalOrders' => 3),
array('category_name' => 'Dessert1', 'totalOrders' => 1),
array('category_name' => 'Category 3', 'totalOrders' => 1),
);
// Auxiliary variable
$result = array();
// Go over the data one by one
foreach ($data as $item)
{
// Use the category name to identify unique categories
$name = $item['category_name'];
// If the category appears in the auxiliary variable
if (isset($result[$name]))
{
// Then add the orders total to it
$result[$name]['totalOrders'] += $item['totalOrders'];
}
else // Otherwise
{
// Add the category to the auxiliary variable
$result[$name] = $item;
}
}
// Get the values from the auxiliary variable and override the
// old $data array. This is not strictly necessary, but if you
// want the indices to be numeric and in order then do this.
$data = array_values($result);
// Take a look at the result
var_dump($data);
I am trying to figure out the best way to write a PHP function that will recursively build a multi-dimensional array with an unknown number of sublevels from a mysql table. Its purpose is to create a data structure which can be looped through to create a navigation menu on a website, with each menu item possibly having a submenu with child menu items.
The fields of note in the table are:
int ItemID
int ParentID
varchar ItemText
text ItemLink
tinyint HasChildren
So an example of a returned array from the function would be:
$menuItems =
array(
itemID# =>
array(
'ItemText' => 'Home',
'ItemLink' => 'index.php',
'Children' => array(
itemID# => array (
'ItemText' => 'Home Sub 1',
'ItemLink' => 'somepage.php',
'Children' => 0
),
itemID# => array (
'ItemText' => 'Home Sub 2',
'ItemLink' => 'somepage2.php',
'Children' => 0
),
)
),
itemID# =>
array(
'ItemText' => 'Contact',
'ItemLink' => 'contact.php',
'Children' => 0
)
)
);
Would greatly appreciate if someone could point me in the right direction to accomplish this. Thanks!
Not too hard. What you do is store the menu items in an array where you can look them up by ID. Then you iterate over the menu items and if they have a non-null ParentID you add them to their parent's list of children. Then you remove all the children from the master list so you have only top-level items left.
Code:
<?php
$menuItems = array
(
1 => array
(
'ItemText' => 'Home',
'ItemLink' => 'index.php',
'ParentID' => null,
),
2 => array
(
'ItemText' => 'Home Sub 1',
'ItemLink' => 'somepage.php',
'ParentID' => 1,
),
3 => array
(
'ItemText' => 'Home Sub 2',
'ItemLink' => 'somepage2.php',
'ParentID' => 1,
),
4 => array
(
'ItemText' => 'Contact',
'ItemLink' => 'contact.php',
'ParentID' => null,
),
);
// Each node starts with 0 children
foreach ($menuItems as &$menuItem)
$menuItem['Children'] = array();
// If menu item has ParentID, add it to parent's Children array
foreach ($menuItems as $ID => &$menuItem)
{
if ($menuItem['ParentID'] != null)
$menuItems[$menuItem['ParentID']]['Children'][$ID] = &$menuItem;
}
// Remove children from $menuItems so only top level items remain
foreach (array_keys($menuItems) as $ID)
{
if ($menuItems[$ID]['ParentID'] != null)
unset($menuItems[$ID]);
}
print_r($menuItems);
?>
Output:
Array
(
[1] => Array
(
[ItemText] => Home
[ItemLink] => index.php
[ParentID] =>
[Children] => Array
(
[2] => Array
(
[ItemText] => Home Sub 1
[ItemLink] => somepage.php
[ParentID] => 1
[Children] => Array
(
)
)
[3] => Array
(
[ItemText] => Home Sub 2
[ItemLink] => somepage2.php
[ParentID] => 1
[Children] => Array
(
)
)
)
)
[4] => Array
(
[ItemText] => Contact
[ItemLink] => contact.php
[ParentID] =>
[Children] => Array
(
)
)
)
Have a function that calls itself every time it gets an array element.
As in:
Your function is called to display a node. Then it checks if the node its calling from has a sub menu, and if does, it calls itself again. And the process repeats until it dies out, and all the previous function calls return.
void printData($mysql_table_node){
if($mysql_table_node.has_node()){
for($i = 0; $i < $mysqql_table_node.num_nodes()){
printData($mysql_table_node->own_node);
}
}
return;
}
Multi-dimensional tree and an unordered HTML list generator
Recursively build a tree from a multi-dimensional array.
Generate a multi-dimensional HTML list code, from the tree (1).
You can't never know how many dimensions is in the given list of items.
Each element can have a son->Grandson->Great grandson an so on.
So, you must use a recursive function to generate a multi-dimensional list.
Here is the code:
<?php
$categories = array(
'1'=> array('name'=>'one','parent'=>null),
'2'=> array('name'=>'two','parent'=>null),
'20'=> array('name'=>'twenty','parent'=>'2'),
'21'=> array('name'=>'twenty one','parent'=>'2'),
'210'=> array('name'=>'two hundred and ten', 'parent'=>'21'),
'211'=> array('name'=>'two hundred and eleven', 'parent'=>'21'),
'212'=> array('name'=>'two hundred and twelve', 'parent'=>'21')
);
$tree=Menu::CreateTree($categories);
print_r($tree);
Menu::GenerateMenuHtmlCode($tree);
class Menu
{
public static function GenerateMenuHtmlCode($tree)
{
echo '<ul>';
foreach ($tree as $key=>$value)
{
echo "<li>".$value['name'];
if(!empty($value['sons']))
self::GenerateMenuHtmlCode($value['sons']);
echo "</li>";
}
echo '</ul>';
}
public static function CreateTree($categories)
{
$tree=array();
self::AddElement(&$categories,&$tree,null);
return $tree;
}
private function AddElement($categories,&$tree,$parent)
{
foreach ($categories as $key=>$value)
{
if($value['parent']==$parent)
{
$tree[$key]=$categories[$key];
$tree[$key]['sons']=array();
self::AddElement($categories,&$tree[$key]['sons'],$key);
}
if(empty($tree['sons'])) unset ($tree['sons']);
}
unset($categories[$parent]);
return ;
}
}
?>
The result:
Array
(
[1] => Array
(
[name] => one
[parent] =>
[sons] => Array()
)
[2] => Array
(
[name] => two
[parent] =>
[sons] => Array
(
[20] => Array
(
[name] => twenty
[parent] => 2
[sons] => Array()
)
[21] => Array
(
[name] => twenty one
[parent] => 2
[sons] => Array
(
[210] => Array
(
[name] => two hundred and ten
[parent] => 21
[sons] => Array()
)
[211] => Array
(
[name] => two hundred and eleven
[parent] => 21
[sons] => Array()
)
[212] => Array
(
[name] => two hundred and twelve
[parent] => 21
[sons] => Array()
)
)
)
)
)
)
and:
<ul>
<li>one</li>
<li>two
<ul>
<li>twenty</li>
<li>twenty one
<ul>
<li>two hundred and ten</li>
<li>two hundred and eleven</li>
<li>two hundred and twelve</li>
</ul>
</li>
</ul>
</li>
</ul>
I know this is a late reply but I found this script above and it was fantastic. I ran into an issue though with it unsetting my children because of the way the ItemID was working, when I ran it with a similarly designed table to the OP. To get around this, and given the amount of RAM in most web servers should be able to handle this, I've taken John Kugelman's great example and modified it slightly. Instead of having to apply children to all of the items and then unsetting them after, I create a new array and build it all in one
Code:
$new_array = array();
foreach ($menuItems as $key => &$menuItem) {
if (($menuItem['ParentID'] != NULL) && ($menuItem['ParentID'] != '')) {
$new_array[$menuItem['ParentID']]['Children'][$menuItem['ItemID']] = &$menuItem;
} else {
$new_array[$menuItem['ItemID']] = &$menuItem;
}
}
print_r($new_array);
Hope this helps someone else because the above certainly helped me