Algorithm to fetch nested array in PHP - php

This question doesn't necessarily have to be related to PHP or MongoDB.
I have MongoDB database with categories collection. Documents inside collection looks like below
{
title : 'Parent 1',
cat_id : '_ABC1',
level : 1
}
{
title : 'Parent 2',
cat_id : '_ABC2'
level : 1
}
{
title : 'Child 1',
cat_id : '_ABC1_CEF1'
level : 2
}
{
title : 'Child 2',
cat_id : '_ABC1_CEF2'
level : 2
}
{
title : 'Child Child 1',
cat_id : '_ABC1_CEF1_GHI1'
level : 3
}
{
title : 'Child Child 2',
cat_id : '_ABC1_CEF1_GHI2'
level : 3
}
Nesting
Now, what I want to do in PHP is get nested array(s) like
$array = array(
array(
'title' => 'Parent 1',
'cat_id' => '_ABC1',
'sub' => array(
'title' => 'Child 1',
'cat_id' => '_ABC1_CEF1',
'sub' => array(
array(
'title' => 'Child Child 1',
'cat_id' => '_ABC1_CEF1_GHI1'
),
array(
'title' => 'Child Child 2',
'cat_id' => '_ABC1_CEF1_GHI2'
)
)
)
),
...
...
)
For that I am using following algorithm (fetch nested level = N)
(N is a fetching parameter number that tells iterator, how deep array has to be fetched)
$array_holder = array();
foreach(MongoGetLevel1Cats as $parent){
$parent['sub'] = array();
foreach(MongoGetLevel2Cats as $child){
$child['sub'] = array();
foreach(MongoGetLevel3Cats as $child_child){
$child_child['sub'] = array();
...
...
array_push($child['sub'], $child_child);
}
array_push($parent['sub'], $child);
}
array_push($array_holder, $parent);
}
Return $array_holder;
As this function will give me the desired result but I don't have control over the deep nested level. If have to fetch nested level of order 100, I have to write foreach loop insider foreach loop for 100 times. Also I don't know performance wise this strategy will be good or not.
Does anybody have any idea how we solve this more strategically? Perhaps with for & foreach loop in combination which doesn't involve nesting foreach loop?

You can use a callback function. For example:
function add_two($result, $how_many_times)
{
$result = $result + 2;//adds 2
echo "current =".$result."<br>";
$how_many_times--;
if ($how_many_times>0)
{
$result = add_two($result, $n);
}
return $result;
}
$resultVar = add_two(5, 5);//should give 15...
echo "resultvar = ".$resultVar."<br>";//outputs: resultvar = 15
In the previous example, I am placing the same function add_two() inside itself and have created a conditional that sees how many iterations I want. Every time I iterate once, I subtract 1 value from $how_many_times. Once $how_man_times == 0, the add_two() function will no longer be called, and the result will have been obtained.
Similarly you can do the same thing with your code. There is no reason for you to have to rewrite the same function all over again if you can just call it inside itself.
Let me know if this is what you were looking for :)

Related

How do I remap my array keys using an array of integers?

Here is my original array of items
$items = [
0 => 'item 1',
1 => 'item 2',
2 => 'item 3'
];
I want to change the order of the items based on their keys, so I'm doing this...
$reorder = [2,0,1];
uksort($items, function($key1, $key2) use ($reorder) {
return (array_search($key1, $reorder) > array_search($key2, $reorder));
});
This works as it should, and produces the proper results.
$items = [
2 => 'item 3',
0 => 'item 1',
1 => 'item 2'
];
However, when returning that in Laravel as json the newly ordered $items reverts back to the original order, which is obviously not what I want.
Is there a way to remap the array keys while reordering them?
How exactly are you returning the array in Laravel? The following example works for me exactly as you want it.
Route::get('/test', function () {
$items = [
0 => 'item 1',
1 => 'item 2',
2 => 'item 3'
];
$reorder = [2,0,1];
uksort($items, function($key1, $key2) use ($reorder) {
return (array_search($key1, $reorder) > array_search($key2, $reorder));
});
return response()->json($items);
// {"2":"item 3","0":"item 1","1":"item 2"}
});
If you have tried it in a browser, keep in mind that some browsers automatically reorder json arrays. In that case you have to get the raw instead of the formatted data.

MAX in PHP returning wrong value

I have an array with key and value pair. I'm building this array dynamically and below is the code.
$x[] = array('type_name' => $value->name,
'percentage'=> intval($percentage));
My intention is to get the maximum value and for that I do
max($x);
However it is returning the wrong value actually the lowest value. Following is my array. Any help would be awesome.
$x = array(
array(
'type_name' => 'type 1'
'percentage' => 10,
),
array(
'type_name' => 'type 2'
'percentage' => 15,
),
array(
'type_name' => 'type 3'
'percentage' => 45,
),
);
Thanks is advance.
From php max() documentation :
// Multiple arrays of the same length are compared from left to right
It means that if you want to compare "percentage" values first instead of "type_name" values, you'll have to change their order in the array.
So, you could build your array like this ("percentage" comes first) and it should work :
$x[] = array(
'percentage'=> intval($percentage),
'type_name' => $value->name
);
For example :
$x = array(
array(
'percentage' => 10,
'type_name' => 'type 1'
),
array(
'percentage' => 15,
'type_name' => 'type 2'
),
array(
'percentage' => 45,
'type_name' => 'type 3'
),
array(
'percentage' => 25,
'type_name' => 'type 4'
)
);
print_r(max($x));
Output :
Array
(
[percentage] => 45
[type_name] => type 3
)
Hope it helps.
You need to read how the max compares against different types of data. In your case, you are trying to compare against one of the array item i.e. percentage inside one of the item so the function max does not know to do this.
There is an example by Revo in the manual which shows you how to do this.
You are creating an array of arrays. max doesn’t know that your arrays should be compared by the 'percentage' key, so it can’t be used here.
Instead, find the maximum value yourself. For example, like this:
$maxPercentage = false;
foreach ($x as $item) {
if ($maxPercentage === false || $item['percentage'] > $maxPercentage) {
$maxPercentage = $item['percentage'];
}
}
Now, $maxPercentage will store maximum percentage. Of, if you want an item with maximum percentage, get it like this:
$maxPercentage = false;
$maxItem = false;
foreach ($x as $item) {
if ($maxPercentage === false || $item['percentage'] > $maxPercentage) {
$maxPercentage = $item['percentage'];
$maxItem = $item;
}
}

Add a multidimension PHP Array at the end of each item of a multidimension array

Here are the two Multi-dimension arrays explained.
// Categories Array
$categories = array(
array('cat_id'=>'1', 'cat_name' => 'Category One', 'cat_data' => 'Some Data'),
array('cat_id'=>'2', 'cat_name' => 'Category Two', 'cat_data' => 'Some Data'),
array('cat_id'=>'3', 'cat_name' => 'Category Tree', 'cat_data' => 'Some Data')
);
// Products Array (One $products array is to be placed inside every new category.
$products = array(
array('p_id'=>'1', 'p_name'=>'Product One'),
array('p_id'=>'2', 'p_name'=>'Product Two'),
array('p_id'=>'3', 'p_name'=>'Product Three')
);
Here The $products needs to be placed inside every element of $category array, with a key of some random key take for eg 'product_list'.
Here is the result like
$category = array(
array('cat_id'=>'1', 'cat_name' => 'Category One', 'cat_data' => 'Some Data', 'product_list'=>array()),
array('cat_id'=>'2', 'cat_name' => 'Category Two', 'cat_data' => 'Some Data', 'product_list'=>array())
);
Please scroll Right for the above code to see the last element added to these elements.
Please tell how to add that multi-dimension array with a key to each n every element of the $category array. Thanks
What is the problem?
foreach ($categories as &$category) {
$category['product_list'] = $products;
}
Try to use this code.
foreach($categories as $key=>$value)
{
$categories[$key]['product_list'] = $products;
}
After trying several attemps, I solved this problem by myself by just a simple code. here it is.
$categories['product_list'] = $products;
Hope, users finding this type of problem this useful. Thanks

Recursive table which child parent relationship

I have a problem with a recursive function that takes too many resources to display a child-parent relation in a dropdown list. For example:
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
I've written some code for a recursive call to the database each time, so that's the reason why my code takes so many resources to run.
Below is my code:
--call recursive
$tmp = $this->get_nav_by_parent(0);
$a_sel = array('' => '-Select-');
$a_sel_cat = array('home' => 'home');
$this->get_child_nav_cat($tmp, 0, $a_sel);
--
public function get_nav_by_parent($parent) {
$all_nav = $this->db
->select('id, title, parent')
->where('parent',$parent)
->order_by('position')
->get('navigation_links')
->result_array();
$a_tmp = array();
foreach($all_nav as $item)
{
if($parent != 0){
$item['title'] = '--' . $item['title'];
}
$a_tmp[] = $item;
}
return $a_tmp;
}
-- Recursive function
public function get_child_nav_cat($a_data, $parent, &$a_sel) {
foreach($a_data as $item) {
$a_sel[$item['page_slug_key']] = $item['title'];
$atmp = $this->get_nav_by_parent($item['id']);
$this->get_child_nav_cat($atmp, $item['id'], $a_sel);
}
return $a_sel;
}
Please give me suggestions for the best solution to display the data as child-parent relationship in select box.
Thanks in advance!
Best way to display parent child relationship is mentain parent and child flag in Database instead of
fetching value using loop.
In your case Home, Home 1 is parent flag and menus belong on child flag.
fetch data from db and your loop look like this:-
$arr = array(0 => array('name' => 'home','parent' => 0),
1 => array('name' => 'menu 1 ','parent' => 1),
2 => array('name' => 'menu 2 ','parent' => 1),
3 => array('name' => 'home 1','parent' => 0),
4 => array('name' => 'menu 3 ','parent' => 2),
5 => array('name' => 'menu 4','parent' => 2)
);
$dd_html = '<select>';
foreach($arr as $k => $v){
if($v['parent'] == 0 )
$dd_html .='<option>'.$v['name'].'</option>';
else
$dd_html .='<option>--'.$v['name'].'</option>';
}
$dd_html .= '</select>';
echo $dd_html;
Output :-
home
-menu 1
-menu 2
home 1
-menu 3
-menu 4
Set ParentID=0 for detecting root item
then execute this:
SELECT * FROM table ORDER BY ParentID, ID
Then iterate through result and when ParentID changed, create new level.

How to reorder this array?

I have a database table as follows:
This returns all column titles in the pic, but the one's that are most important are slug, and parent (not sure about id_button).
The array gets ordered automatically by id_button ASC, which really irks me. But, anyways, this is not important, as I need to order it completely different, or re-order it after the array is populated.
The array returns this, by order of id_button:
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
2 => array(
'id_button' => 3,
'parent' => 'google.com',
'position' => 'after',
'slug' => 'another_test',
),
3 => array(
'id_button' => 4,
'parent' => 'testing'
'position' => 'child_of',
'slug' => 'google.com',
)
);
I need to order it so that if a slug is found within any parent, than the slug that is in the parent needs to be loaded before the one that has it defined within the parent.
Its not important if it is directly before it. For example, you see testing is the first slug that gets returned, and yet the parent for this is the last slug (google.com). So as long as the slug row where the parent is defined gets ordered so that it is BEFORE the row that has the slug value in the parent column, everything is fine.
So in this situation, it can be reordered as any of these 3 ordered arrays below:
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
2 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
3 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
)
);
OR this...
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
2 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
),
3 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
)
);
OR even this...
$new_menu_buttons = array(
0 => array(
'id_button' => 1,
'parent' => 'help',
'position' => 'child_of',
'slug' => 'testing',
),
1 => array(
'id_button' => 4,
'parent' => 'testing',
'position' => 'child_of',
'slug' => 'google.com',
),
2 => array(
'id_button' => 3,
'parent' => 'google.com'
'position' => 'after',
'slug' => 'another_test',
),
3 => array(
'id_button' => 2,
'parent' => 'packages',
'position' => 'after',
'slug' => 'sub_test_1',
)
);
All 3 of these ordered arrays will work because the array with the slug that matches the parent is before the array with the matching parent, and since the slug value, sub_test_1 doesn't match any of the parent values this array order is unimportant, so that array can be located anywhere within the array.
How can I do this? I'm thinking of just looping through the array somehow and trying to determine if the slug is in any of the parents, and just do a reordering somehow...
In short, the slug needs to be ordered before the parent ONLY if there is a parent that matches a slug within the array. Otherwise, if no match is found, the order isn't important.
As Niko suggested, databases support powerful sorting functionality, so you normally can best solve this by telling the database in which order to return the data. If the data is queried with SQL, that's the ORDER BY clause. This is specified in the documentation of your database, assuming you're using MySQL 5.0: http://dev.mysql.com/doc/refman/5.0/en/sorting-rows.html
If you can not influence the order on the database level, you're in the need to sort the array in PHP. You actually have an array of arrays, in which the outer array is just a list having the id (primary key) of each row and the other fields as a fieldname -> value array as a value (inner array).
Your sort is *user-defined` - you specify the sort order. A common way is to have a sort function that compares two entries which each other. That sort function needs to decide which of those two is of a higher sort-order than the other (or both have the same weight). In you case one item is higher than the other if one is the child of the other.
That's the general principle. You define the sort function that decides (the so called callback function), and PHP takes care to feed it with the array data to sort with the usortDocs function.
A sub-problem you need to solve then is to decide whether or not a child exists in the whole array (an item with a slug having the same value as parent). As this all looks like it can be a bit more complex, it's wise to encapsulate this all into a class of it's own.
Example / Demo:
class menuButtons
{
/**
* #var array
*/
private $buttons;
public function __construct(array $buttons)
{
$this->buttons = $buttons;
}
public function sortChildsFirst()
{
$buttons = $this->buttons;
usort($buttons, array($this, 'sortCallback'));
return $buttons;
}
private function sortCallback($a, $b)
{
// an element is more than any other if it's parent
// value is any other slugs value
if ($this->slugExists($a['parent']))
return 1;
return -1;
}
private function slugExists($slug)
{
foreach($this->buttons as $button)
{
if ($button['slug'] === $slug)
return true;
}
return false;
}
}
$buttons = new menuButtons($new_menu_buttons);
$order = $buttons->sortChildsFirst();
Note: This code is exploiting the fact that your sort order is only roughly specified. You only wrote that you need to have children before parents, so if you take all children first, this will always be the case. It's not that each parent will directly follow the child.
Nevertheless, this skeleton class can work as a base to further improve the search functionality as it's fully encapsulated. You can even change the whole sort method, e.g. to completely write one of your own even w/o usort, like outlined below. The main code does not need to change as it's only making use of the sortChildsFirst method.
You can sort an array once populated using the usort() function.
http://php.net/manual/en/function.usort.php
Since your structure is tree-alike, the first thing that comes to mind is to build a tree out of it. It goes like this:
$tree = array();
foreach($array as $e) {
$p = $e['parent'];
$s = $e['slug'];
if(!isset($tree[$p]))
$tree[$p] = new stdclass;
if(!isset($tree[$s]))
$tree[$s] = new stdclass;
$tree[$s]->data = $e;
$tree[$p]->sub[] = $tree[$s];
}
This creates a set of objects, with the members data and sub = list of child objects.
Now we iterate the tree and for each "root" node, add it and its children to the sorted array:
$out = array();
foreach($tree as $node)
if(!isset($tree[$node->data['parent']]))
add($out, $node);
where add() is
function add(&$out, $node) {
if(isset($node->data))
$out[] = $node->data;
if(isset($node->sub))
foreach($node->sub as $n)
add($out, $n);
}
hope this helps.
Ok, first let me thank you all for your detailed explanations. They are very intuitive. However, I found another way, can you guys let me know if you spot anything wrong with this method here please?
Click here to see a Demo of this working!
$temp_buttons = array();
foreach($new_menu_buttons as $buttons)
$temp_buttons[$buttons['parent']] = $buttons['slug'];
dp_sortArray($new_menu_buttons, $temp_buttons, 'slug');
// The $new_menu_buttons array is now sorted correctly! Let's check it...
var_dump($new_menu_buttons);
function dp_sortArray(&$new_menu_buttons, $sortArray, $sort)
{
$new_array = array();
$temp = array();
foreach ($new_menu_buttons as $key => $menuitem)
{
if (isset($sortArray[$menuitem[$sort]]))
{
$new_array[] = $menuitem;
$temp[$menuitem['parent']] = $menuitem['slug'];
unset($new_menu_buttons[$key]);
}
}
$ordered = array();
if (!empty($new_array))
{
foreach ($new_array as $key => $menuitem)
{
if (isset($temp[$menuitem[$sort]]))
{
$ordered[] = $menuitem;
unset($new_array[$key]);
}
}
}
else
{
$new_menu_buttons = $new_menu_buttons;
return;
}
$new_menu_buttons = array_merge($ordered, $new_array, $new_menu_buttons);
}
Seems to work in all instances that I tested, but ofcourse, their could be a flaw in it somewhere. What do you all think of this?

Categories