I'm trying to optimize an e-commerce category system with unlimited category depth (barring system memory limitations). I retrieve all the categories at once and order them as a multi-dimensional array that roughly looks like:
[array] (
[0] (
'CategoryId' => 1,
'ParentCategoryId' => 0,
'Title' => 'Category A',
'SubCategories' => [array] (
[0] (
'CategoryId' => 2,
'ParentCategoryId' => 1,
'Title' => 'Category B',
'SubCategories' => [array] (
[0] (
'CategoryId' => 3,
'ParentCategoryId' => 2,
'Title' => 'Category C'
)
)
)
)
)
)
Each item in the array is actually an object, but for simplicity I wrote it out kind of like an array format.
I'm able to traverse my tree downwards using this function:
/**
* Find Branch using Recursive search by Object Key
* #param String Needle
* #param Array Haystack
* #return Array
*/
public static function findBranchByKey($key, $needle, $haystack)
{
foreach ($haystack as $item)
{
if ( $item->$key == $needle || ( is_object($item) && $item = self::findBranchByKey($key, $needle, $item->SubCategories)) )
{
return $item;
}
}
return false;
}
This finds the object with a matching key and returns it (which may contain more subcategories).
My issue is figuring out how to traverse the other direction. For example, using the data above, let's say I am displaying "Category C" and want to create bread crumbs of it's parents. I can't think of a good way to take my tree array, jump to a specific subcategory, then iterate upwards to get each parent. A resulting array from something like this could be like this so it's easy to spit them out as bread crumbs:
array( 'Category A', 'Category B', 'Category C' )
I could probably do this using SQL in my database but I'd like to retrieve the tree once, cache it, and perform traversal on that object whenever I need to rather than making tons of queries.
TL;DR; How can I traverse upwards in a multidimensional array of categories?
It can be done by recursion.
Let's say, this function should work:
function getPath($id, $tree, &$path = array()) {
foreach ($tree as $item) {
if ($item['CategoryId'] == $id) {
array_push($path, $item['CategoryId']);
return $path;
}
if (!empty($item['SubCategories'])) {
array_push($path, $item['CategoryId']);
if (getPath($id, $item['SubCategories'], $path) === false) {
array_pop($path);
} else {
return $path;
}
}
}
return false;
}
This:
$data = array(
array(
'CategoryId' => 10,
'ParentCategoryId' => 0,
'SubCategories' => array(
array(
'CategoryId' => 12,
'ParentCategoryId' => 1,
'SubCategories' => array()
),
)
),
array(
'CategoryId' => 1,
'ParentCategoryId' => 0,
'SubCategories' => array(
array(
'CategoryId' => 2,
'ParentCategoryId' => 1,
'SubCategories' => array()
),
array(
'CategoryId' => 3,
'ParentCategoryId' => 1,
'SubCategories' => array()
),
array(
'CategoryId' => 4,
'ParentCategoryId' => 1,
'SubCategories' => array(
array(
'CategoryId' => 5,
'ParentCategoryId' => 4,
'SubCategories' => array()
),
)
)
)
)
);
$result = getPath(5, $data);
print_r($result);
will result in:
Array ( [0] => 1 [1] => 4 [2] => 5 )
Related
I'm trying to make a simple tree structure where every task has a certain percentage of completion and its parent has to inherit average completion of its direct children, as seen conceptually on the picture below. (0s are percentages of completion, for example subtask2 could be 100% and subtask2 0%, which would give task1 50% completion and therefore stackoverflow would have 25%, given task2 is at 0)
The issue I'm having is that I need to start, apparently, from the deepest children, but I can't seem to figure out how to implement such reversal traversal from leafs to root.
I have tried with normal recursive as well as double for loop and both only achieve first level calculations (in the picture example task1 is calculated, but stackoverflow will remain 0).
Note: Only leafs can actually have completion percentage, since every other element, which is not a leaf, inherits percentage from its children. (how paradoxical)
If any of you have any ideas on how to implement such an algorithm, be it conceptually or actual code, I would very much appreciate any input.
Below is structure of this array (only kept relevant information):
[0] => Array
(
[title] => stackoverflow
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => task2
[completion] => 0
)
[1] => Array
(
[title] => task1
[completion] => 0
[children] => Array
(
[0] => Array
(
[title] => subtask2
[completion] => 100
)
[1] => Array
(
[title] => subtask1
[completion] => 0
)
)
)
)
)
I seem to be having a similar issue than the issue in this thread: Percentages and trees however, I need my task to have actual percentages, not only completed/non-completed. All math is completely linear, meaning that parent's percentage = (addition of all percentages of children) / (number of children)
Also var_export:
array (
0 =>
array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
)
Here is a recursive function that passes the parent by reference until it finds a leaf and updates totals working backward.
function completionTree(&$elem, &$parent=NULL) {
// Handle arrays that are used only as a container... if we have children but no uuid, simply descend.
if (is_array($elem) && !isset($elem['uuid'])) {
foreach($elem AS &$child) {
completionTree($child, $elem);
}
}
// This array has children. Iterate recursively for each child.
if (!empty($elem['children'])) {
foreach ($elem['children'] AS &$child) {
completionTree($child, $elem);
}
}
// After recursion to handle children, pass completion percentages up to parent object
// If this is the top level, nothing needs to be done (but suppress that error)
if (#$parent['completed'] !== NULL) {
// Completion must be multiplied by the fraction of children it represents so we always add up to 100. Since values are coming in as strings, cast as float to be safe.
$parent['completed'] = floatval($parent['completed']) + (floatval($elem['completed']) * (1/count($parent['children'])));
}
}
// Data set defined statically for demonstration purposes
$tree = array(array (
'uuid' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'title' => 'stackoverflow',
'completed' => '0',
'parent' => NULL,
'children' =>
array (
0 =>
array (
'uuid' => '72ce49a6-76e5-495e-a3f8-0f13d955a3b5',
'title' => 'task2',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
),
1 =>
array (
'uuid' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
'title' => 'task1',
'completed' => '0',
'parent' => '157ed2b2-0d0c-4f0c-b1d2-7126255f4023',
'children' =>
array (
0 =>
array (
'uuid' => 'ac5e9d37-8f14-4169-bcf2-e7b333c5faea',
'title' => 'subtask2',
'completed' => '0',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
1 =>
array (
'uuid' => 'f74b801f-c9f1-40df-b491-b0a274ffd301',
'title' => 'subtask1',
'completed' => '100',
'parent' => '4975d08d-55f0-4cd8-9de5-2d056111ec2d',
),
),
),
),
),
);
// Launch recursive calculations
completionTree($tree);
// Dump resulting tree
var_dump($tree);
Though this is answered, I'd like to leave a solution that seems a bit more intuitive (IMHO). Instead of passing down the parent, just handle the children first:
/**
* #param array $nodes
*
* #return array
*/
function calcCompletion(array $nodes): array {
// for each node in nodes
return array_map(function (array $node): array {
// if it has children
if (array_key_exists('children', $node) && is_array($node['children'])) {
// handle the children first
$node['children'] = calcCompletion($node['children']);
// update this node by *averaging* the children values
$node['completed'] = array_reduce($node['children'], function (float $acc, array $node): float {
return $acc + floatval($node['completed']);
}, 0.0) / count($node['children']);
}
return $node;
}, $nodes);
}
Well, this might be a little overhead, but you can use RecursiveArrayIterator. First, you must extend it to handle your tree structure:
class MyRecursiveTreeIterator extends RecursiveArrayIterator
{
public function hasChildren()
{
return isset($this->current()['children'])
&& is_array($this->current()['children']);
}
public function getChildren()
{
return new static($this->current()['children']);
}
}
Then using RecursiveIteratorIterator you can create an iterator which will process your tree starting from leaves:
$iterator = new RecursiveIteratorIterator(
new MyRecursiveTreeIterator($tasks),
RecursiveIteratorIterator::CHILD_FIRST
);
Then with this one, you can add your calculation logic:
$results = [];
$temp = [];
$depth = null;
foreach ($iterator as $node) {
if ($iterator->getDepth() === 0) {
// If there were no children use 'completion'
// else use children average
if (
is_null($depth)
|| !isset($temp[$depth])
|| !count($temp[$depth])
) {
$percentage = $node['completed'];
} else {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
}
$results[$node['title']] = $percentage;
continue;
}
// Set empty array for current tree depth if needed.
if (!isset($temp[$iterator->getDepth()])) {
$temp[$iterator->getDepth()] = [];
}
// If we went up a tree, collect the average of children
// else push 'completion' for children of current depth.
if ($iterator->getDepth() < $depth) {
$percentage = array_sum($temp[$depth]) / count($temp[$depth]);
$temp[$depth] = [];
$temp[$iterator->getDepth()][] = $percentage;
} else {
$temp[$iterator->getDepth()][] = $node['completed'];
}
$depth = $iterator->getDepth();
}
Here is a demo.
I have an array of arrays, as such
$statuses = array(
[0] => array('id'=>10, 'status' => 'active'),
[1] => array('id'=>11, 'status' => 'closed'),
[2] => array('id'=>12, 'status' => 'active'),
[3] => array('id'=>13, 'status' => 'stopped'),
)
I want to be able to make a new array of arrays and each of those sub arrays would contain the elements based on if they had the same status.
The trick here is, I do not want to do a case check based on hard coded status names as they can be random. I want to basically do a dynamic comparison, and say "if you are unique, then create a new array and stick yourself in there, if an array already exists with the same status than stick me in there instead". A sample result could look something like this.
Ive really had a challenge with this because the only way I can think to do it is check every single element against every other single element, and if unique than create a new array. This gets out of control fast if the original array is larger than 100. There must be some built in functions that can make this efficient.
<?php
$sortedArray = array(
['active'] => array(
array(
'id' => 10,
'status' => 'active'
),
array(
'id' => 12,
'status' => 'active'
)
),
['closed'] => array(
array(
'id' => 11,
'status' => 'active'
)
),
['stopped'] => array(
array(
'id' => 13,
'status' => 'active'
)
),
)
$SortedArray = array();
$SortedArray['active'] = array();
$SortedArray['closed'] = array();
$SortedArray['stopped'] = array();
foreach($statuses as $Curr) {
if ($Curr['status'] == 'active') { $SortedArray['active'][] = $Curr; }
if ($Curr['status'] == 'closed') { $SortedArray['closed'][] = $Curr; }
if ($Curr['status'] == 'stopped') { $SortedArray['stopped'][] = $Curr; }
}
You can also do it with functional way though it's pretty the same like Marc said.
$sorted = array_reduce($statuses, function($carry, $status) {
$carry[$status['status']][] = $status;
return $carry;
}, []);
I have a requirement to allow my end users to input formula much like a spreadsheet. I have an array like this:
$table = array(
1=>array(
"id"=>1,
"Name"=>"Regulating",
"Quantity"=>"[2]Quantity+[3]Value",
"Value"=>"[2]Cost"
),
...)
The first level array key is always the same value as the id key in that array.
A tabulated example follows:
id Name Quantity Value
1 Regulating [2]Quantity+[3]Value [2]Cost
2 Kerbs 3 6
3 Bricks 9 7
4 Sausages [3]Cost 3
5 Bamboo [4]Quantity [7]Cost
6 Clams [4]Quantity NULL
7 Hardcore [3]Quantity*0.5 12
8 Beetles [6]Quantity*[4]Value [2]Value
The Quantity and Value keys represent formula which reference the [id] and either Quantity, Value or Cost.
Cost is derived by multiplying the Value and Quantity.
I am using:
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $string, $matches, PREG_SET_ORDER);
which outputs an array like so for[1][Quantity]:
Array
(
[0] => Array
(
[0] => [2]Quantity
[1] => 2
[2] => Quantity
)
[1] => Array
(
[0] => [3]Value
[1] => 3
[2] => Value
)
)
Iterating through the table using something similar to:
$calcString = $table[1]['Quantity'];`
foreach ($matches as $match) {
$calcString = str_replace($match[0], $table[$match[1]][$match[2]], $calcString);
}
I can get the string to be calculated and am using a matheval class to do the sum.
For example
[1]Quantity = [2]Quantity + [3]Value
[2]Quantity = 3
[3]Value = 7 // [1]Quantity = 3 + 7 = 10
[1]Value = [2]Cost
[2]Cost = [2]Quantity * [2]Value // 3 * 6 = 18
Basically the variables in the table refer to other [id]key in the same table.
But here is my issue
I need to resolve references to other parts of the table (which may or may not themselves be formula) to fill in the blanks. This is outside my comfort zone and I would appreciate any advice (or even better functional code) which provides enlightenment on how I might be able to achieve this.
Thanks
Deep down, you already know how to solve this, you're just intimidated by the task.
A recursive approach would be to expand references instantly. For example,
expand('[1]Value') # returns '[2]Cost'
expand('[2]Cost') # returns '[2]Quantity * [2]Value'
expand('[2]Quantity') # returns 3
expand('[2]Value') # returns 6
eval('3 * 6')
# returns 18
# returns 18
# returns 18
An iterative (non-recursive) approach is to expand one reference at a time and repeat until there are unresolved references in the string.
expand('[1]Value') // returns '[2]Cost'
expand('[2]Cost') // returns '[2]Quantity + [2]Value'
expand('[2]Quantity + [2]Value') // returns 3 for [2]Quantity
expand('3 * [2]Value') // returns 6 for [2]Value
eval('3 * 6')
# returns 18
Normally, I prefer iterative solutions, because they're much less prone to stack overflows. However, recursive solutions are usually easier to write.
Here's a quickly slapped-together recursive evaluator: https://gist.github.com/stulentsev/b270bce4be67bc1a96ae (written in ruby, though)
If calcString's are reasonably sized and you don't expect replacements to get too elaborate, you could use a while loop to simulate the recursion. Here's an example that outputs the string along the way as it is being modified:
$calcString = $table[8]['Quantity'];
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $calcString, $matches, PREG_SET_ORDER);
print_r($calcString . "\n");
while (!empty($matches)){
foreach ($matches as $match) {
preg_match_all("/\[(.*?)\](Cost)/", $match[0], $matchCost, PREG_SET_ORDER);
if (!empty($matchCost)){
$cost = $table[$matchCost[0][1]]['Quantity'] . "*" . $table[$matchCost[0][1]]['Value'];
$calcString = str_replace($match[0], $cost, $calcString);
} else {
$calcString = str_replace($match[0], $table[$match[1]][$match[2]], $calcString);
}
print_r($calcString . "\n");
}
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $calcString, $matches, PREG_SET_ORDER);
}
Output:
[6]Quantity*[4]Value
[4]Quantity*[4]Value
[4]Quantity*3
[3]Cost*3
9*7*3
The table variable:
$table = array(
1 => array(
"id" => 1,
"Name" => "Regulating",
"Quantity" => "[2]Quantity+[3]Value",
"Value" => "[2]Cost"
),
2 => array(
"id" => 2,
"Name" => "Kerbs",
"Quantity" => 3,
"Value" => 6
),
3 => array(
"id" => 3,
"Name"=>"Bricks",
"Quantity"=> 9,
"Value"=> 7
),
4 => array(
"id" => 2,
"Name" => "Sausages",
"Quantity" => "[3]Cost",
"Value" => 3
),
5 => array(
"id" => 2,
"Name" => "Bamboo",
"Quantity" => "[4]Quantity",
"Value" => "[7]Cost"
),
6 => array(
"id" => 2,
"Name" => "Clams",
"Quantity" => "[4]Quantity",
"Value" => NULL
),
7 => array(
"id" => 2,
"Name" => "Hardcore",
"Quantity" => "[3]Quantity*0.5",
"Value" => 12
),
8 => array(
"id" => 2,
"Name" => "Beetles",
"Quantity" => "[6]Quantity*[4]Value",
"Value" => "[2]Value"
)
);
A dangerously easy, and your-situation-specific well-performable solution!
<?php
class solver {
private
// The final output array
$arr_evaled,
// When a cell gains its final value, the corresponding entry in the following array gets marked as being done!
$arr_done;
private $solving_iterations_count;
public function solver($array) {
$this->arr_done = array();
foreach($array as $k => $arr)
$this->arr_done[$k] = array('Quantity' => false, 'Value' => false);
// Firstly,expand all of the "[x]Cost"s to "([x]Quantity*[x]Value)"s!
$this->arr_evaled = array_map(
function($v){ return preg_replace('#\[(\d*?)\]Cost#', '([$1]Quantity*[$1]Value)', $v); },
$array
);
$this->solving_iterations_count = 0;
$this->solve();
}
private function isDone() {
foreach($this->arr_done as $a)
if($a['Quantity'] == false || $a['Value'] == false)
return false;
return true;
}
private function isCellDone($id, $fieldName) {
return $this->arr_done[$id][$fieldName];
}
private function markCellAsDone($id, $fieldName, $evaluation) {
$this->arr_done[$id][$fieldName] = true;
$this->arr_evaled[$id][$fieldName] = $evaluation;
}
private function isEvaluable($str) {
return preg_match('#^[0-9*+-\/\(\)\.]*$#', $str) == 1 || strtolower($str)=='null';
}
private function replace($from, $to) {
foreach($this->arr_evaled as &$arr) {
$arr['Quantity'] = str_replace($from, $to, $arr['Quantity']);
$arr['Value'] = str_replace($from, $to, $arr['Value']);
}
}
private function solve() {
$isSolvable = true; // YOUR TODO: I believe coding this part is also fun!) (e.g: check for "reference cycles")
if(!$isSolvable) return null;
while( !$this->isDone() )
{
foreach($this->arr_evaled as $arr) {
foreach(['Quantity', 'Value'] as $fieldName) {
if(!$this->isCellDone($arr['id'], $fieldName)) {
if($this->isEvaluable($arr[$fieldName])) {
$evaluation = eval("return {$arr[$fieldName]};");
$this->markCellAsDone($arr['id'], $fieldName, $evaluation);
$this->replace("[{$arr['id']}]$fieldName", "$evaluation");
}
}
}
}
$this->solving_iterations_count++;
}
foreach($this->arr_evaled as &$row)
$row['Cost'] = $row['Quantity'] * $row['Value'];
return $this->arr_evaled;
}
public function print_tabulated() {
echo "The count of solving iterations: {$this->solving_iterations_count}<br/><br/>";
echo '<table border="1"><tr><th>id</th><th>Name</th><th>Quantity</th><th>Value</th><th>Cost</th></tr>';
foreach($this->arr_evaled as $arr)
echo "<tr><td>{$arr['id']}</td><td>{$arr['Name']}</td><td>{$arr['Quantity']}</td><td>{$arr['Value']}</td><td>{$arr['Cost']}</td></tr>";
echo '</table>';
}
}
// Testing
$arr = array(
1 => array( 'id' => 1, 'Name' => 'Regulating', 'Quantity' => '[2]Quantity+[3]Value', 'Value' => '[2]Cost' ),
2 => array( 'id' => 2, 'Name' => 'Kerbs', 'Quantity' => '3', 'Value' => '6' ),
3 => array( 'id' => 3, 'Name' => 'Bricks', 'Quantity' => '9', 'Value' => '7' ),
4 => array( 'id' => 4, 'Name' => 'Sausages', 'Quantity' => '[3]Cost', 'Value' => '3' ),
5 => array( 'id' => 5, 'Name' => 'Bamboo', 'Quantity' => '[4]Quantity', 'Value' => '[7]Cost' ),
6 => array( 'id' => 6, 'Name' => 'Clams', 'Quantity' => '[4]Quantity', 'Value' => 'NULL' ),
7 => array( 'id' => 7, 'Name' => 'Hardcore', 'Quantity' => '[3]Quantity*0.5', 'Value' => '12' ),
8 => array( 'id' => 8, 'Name' => 'Beetles', 'Quantity' => '[6]Quantity*[4]Value', 'Value' => '[2]Value' ),
);
echo '<pre>';
(new solver($arr))->print_tabulated();
Here is the output:
Im trying to list categories with sub categories in my app - I can use either PHP or Javascript / Jquery for the following:
I have an array of categories with sub categories appended as additional arrays
the trick is that it can go as deep as there are sub categories.
and sub categories can also have sub categories.
Therefore for each category it can have as many children each of whom can have many children arrays.
What would be the best way to loop through them to create a dropdown list?
Here is the structure when dumping the main array:
array (size=2)
0 =>
array (size=4)
'id' => int 1
'name' => string 'Stationery' (length=10)
'parent_id' => int 0
'childs' =>
array (size=1)
0 =>
array (size=4)
...
1 =>
array (size=3)
'id' => int 4
'name' => string 'boots' (length=5)
'parent_id' => int 0
notice sub zero has a "childs" array
when dumping this array i get:
array (size=1)
0 =>
array (size=4)
'id' => int 2
'name' => string 'pens' (length=4)
'parent_id' => int 1
'childs' =>
array (size=1)
0 =>
array (size=4)
...
Notice this too has a child attached which when dumped looks like:
array (size=1)
0 =>
array (size=4)
'id' => int 3
'name' => string 'penfillers' (length=10)
'parent_id' => int 2
'childs' =>
array (size=1)
0 =>
array (size=3)
...
Sneaky - this one also has another child!
This can go as deep as there are sub categories
How would i loop through them and have the output in a dropdown list?
Im stumped as to how to loop infinitely until the chain ends.
Thanks
Jason
You should recursively yield all the options in the array. There are 2 ways to implement it. Depends on your PHP version.
To make the core logic cleaner, let's say we'd render the output with these utilities:
//
// some function to tidy up outputs
//
// simply make the recursive level visible
function pad_level($string, $level) {
// no pad for 0 level
if ($level == 0) return $string;
// pad some '-' to show levels
$pad = str_repeat('-', $level);
return $pad . ' ' . $string;
}
// render a leaf into standardized info array for an option
function option_from($item, $level) {
return array(
'value' => $item['id'],
'display_value' => pad_level($item['name'], $level),
);
}
// render options into HTML select
function render_select($name, $options) {
$output = '';
foreach ($options as $option) {
$output .= ' '.
'<option value="'.htmlspecialchars($option["value"]).'">'.
htmlspecialchars($option["display_value"]).
'</option>'."\n";
}
return '<select name="'.htmlspecialchars($name).'">'."\n".
$output."</select>\n";
}
// render options into plain text display
function render_plain_text($name, $options) {
$output = '';
foreach ($options as $option) {
$output .= $option["value"].' => '.$option["display_value"]."\n";
}
return $output;
}
These are the 2 methods:
//
// core logic
//
// Method 1: Generator. Available with PHP 5 >= 5.5.0
function options_in($array, $level=0) {
foreach ($array as $leaf) {
yield option_from($leaf, $level);
// yield the children options, if any
if (isset($leaf['childs']) && is_array($leaf['childs'])) {
foreach (options_in($leaf['childs'], $level+1) as $option) {
yield $option;
}
}
}
}
// Method 2: Normal recursion then merge arrays. For PHP 4 or after
function get_options($array, $level=0) {
$output = array();
// yield for the option array
foreach ($array as $leaf) {
$output[] = option_from($leaf, $level);
// yield the children
if (isset($leaf['childs']) && is_array($leaf['childs'])) {
$childs = get_options($leaf['childs'], $level+1);
$output = array_merge($output, $childs); // this could be slow
}
}
return $output;
}
And this is how you actually render some HTML from it:
// dummy input
$input = array(
array(
'id' => 1,
'name' => 'Stationery',
'parent_id' => 0,
'childs' => array(
array(
'id' => 2,
'name' => 'Pencil',
'parent_id' => 1,
),
array(
'id' => 3,
'name' => 'Pen',
'parent_id' => 1,
),
array(
'id' => 5,
'name' => 'Notepad',
'parent_id' => 1,
'childs' => array(
array(
'id' => 8,
'name' => 'Blue Pad',
'parent_id' => 3,
),
array(
'id' => 9,
'name' => 'Red Pad',
'parent_id' => 3,
),
array(
'id' => 10,
'name' => 'iPad',
'parent_id' => 3,
),
),
),
),
),
array(
'id' => 4,
'name' => 'boots',
'parent_id' => 0,
),
);
// method 1, preferred
echo "\nMethod 1\n";
echo render_select('mySelect', options_in($input));
echo render_plain_text('mySelect', options_in($input));
// method 2
echo "\nMethod 2\n";
echo render_select('mySelect', get_options($input));
echo render_plain_text('mySelect', get_options($input));
Here is a really simple example of how you could do it using recursion. I'm sure there is better ways but this is a very simple function so you can see the concept. The function calls itself until the job is done (I'm using the square bracket array notation here so make sure your PHP version supports it)
<?php
function getItems(array $items)
{
$return = [];
foreach ($items as $item) {
$return[] = $item;
if (isset($item['childs']) && count($item['childs']) > 0) {
$return = array_merge(
$return,
getItems($item['childs'])
);
}
}
return $return;
}
$array = [
0 => [
'id' => 1,
'childs' => []
],
1 => [
'id' => 2,
'childs' => [
0 => [
'id' => 3,
'childs' => []
]
]
]
];
print_r(getItems($array));
Then just loop over the results to create your select options. Hope this helps
create a sub function, input of the sub function would be object ,this function will check if it is an array or a simple element, if it is an array then it will call the function again on that element else return the element.
just elaborate "Gerald Schneider"'s answer.
I have simple array
array(
array( 'id'=>5, 'something' => 2, 'dsadsa' => 'fsfsd )
array( 'id'=>20, 'something' => 2, 'dsadsa' => 'fsfsd )
array( 'id'=>30, 'something' => 2, 'dsadsa' => 'fsfsd )
)
How to create associative array by id field (or something else) from it in the right way?
array(
'5' => array( 'something' => 2, 'dsadsa' => 'fsfsd )
'20' => array( 'something' => 2, 'dsadsa' => 'fsfsd )
'30' => array( 'something' => 2, 'dsadsa' => 'fsfsd )
)
Something along these lines.
$new_array = array();
foreach ($original_array as &$slice)
{
$id = (string) $slice['id'];
unset($slice['id']);
$new_array[$id] = $slice;
}
#NikitaKuhta, nope. There is no slice function which returns a column of values in a 2D keyed table associated with a given key or column heading. You can use some of the callback array_... functions, but you will still need to execute a custom function per element so its just not worth it. I don't like Core Xii's solution as this corrupts the original array as a side effect. I suggest that you don't use references here:
$new_array = array();
foreach ($original_array as $slice) {
$id = (string) $slice['id'];
unset($slice['id']);
$new_array[$id] = $slice;
}
# And now you don't need the missing unset( $slice)