Recursive list of items in PHP, unknown depth and nodes - php

gurus!
First of all, I've spent half a day googlin'n'stackoverflowing but couldn't find a solution. This is my first time working with recursions. Hope someone can help.
I have a MySQL table, this is kind of referal system:
Table 'users'
ID SPONSORID
---------
1 2
2 1
3 1
4 1
... ...
There are several things to keep in mind:
Users 1 and 2 are sponsors/referals of each other.
Each user can have unlimited number of referals.
Each user's referal becomes a sponsor as well and can also have unlimited number of referals
Depth is unlimited.
The task is to recursively build a tree of a single "team", like:
User 1
User 2
User 1
User 5
...
....
User 3
User 295
User 356
....
User 4
and so on...
Here's what I'm trying to do:
$team = Array();
function build_team( $userID, $team ){
if ( !in_array($userID, $team ) :
// get 1st level of referals
// returns associative array ('id', 'login', 'sponsorid')
$referals = get_user_referals( $userID );
for ( $i=0; $i<count($referals); $i++ ) :
$team[] = $referals[$i];
build_team( $referals[$i]['id'] );
endfor;
endif;
}
And also tried putting that IF inside the FOR block, but it still goes in infinite loop. As I understand I need a condition to quit recursion when there's no depth level but I can't understand how to calculate/determine it. Any suggestions?

Keep users ids, whom are already "built", somewhere. As you said yourself - "Users 1 and 2 are sponsors/referals of each other.", so there is your infinite loop.
Something like
if (!in_array($userId, $already_looped_users){
//continue loop...
}
Adding some code:
$team = Array();
function build_team( $userID, $team ){
if ( !in_array($userID, $team ) :
// get 1st level of referals
// returns associative array ('id', 'login', 'sponsorid')
$referals = get_user_referals( $userID );
if (count($referals) > 0){ //added this line
for ( $i=0; $i<count($referals); $i++ ) :
$team[] = $referals[$i];
$team[$referals[$i] = build_team( $referals[$i]['id'], $team ); // i've edited this line
endfor;
return $team;
}
endif;
}

Related

Somewhat-Hierarchical Data With MySQL

I'm working with some somewhat hierarchical data for a work project and trying to find a more efficient way of dealing with it as my first attempt is probably dreadful in more ways than one. I've looked at a number of hierarchical data questions on this site so I know that with my structure it's nigh-impossible to get the information in a single query.
The table I'm querying from is on an AS/400 and each entry stores a single part of a single step, so if I had PartOne and three Components go into it there is an entry for each like:
PartOne ComponentOne, PartOne ComponentTwo, PartThree ComponentThree.
Its important to note if there are components for ComponentOne a subsequent row could contain:
ComponentOne SubComponentOne, ComponentOne SubComponentTwo.
With this in mind I'm trying to get all of the components in a tree-like structure for given finished parts, basically getting everything that goes into the final product.
I cannot create a flattened table for this, as what I'm trying to do is dynamically generate that flattened table. I do however have access to the list of finished parts. So my current algorithm goes like this
Fetch part number, query table for the components when that's the created part.
Take those components and query for each as the created part and get their components.
Repeat until query returns no entries.
In this case that's 7 queries deep. I'm wondering from anyone on the outside looking in if a better algorithm makes sense for this and also its been a while since I've done recursion but this seems reasonable for recursion at least in the creation of the queries. Would it be reasonable to look into creating a recursive function that passes back the query results from each level and somewhere in there store the information in an array/table/database entries?
Do you want a tree structure in php?
Then the next code sample might be interesting.
This builds a tree for records in the categories table starting with id -1000 as the root element, using only as many queries as the number of levels deep you want the information.
Table structure:
TABLE categories (
id INT NOT NULL PRIMARY KEY,
parent_id INT NULL,
name NVARCHAR(40) NOT NULL
)
PHP code:
class Bom {
public $Id;
public $Name;
public $Components = array();
function __construct( $id, $name ) {
$this->Id = $id;
$this->Name = $name;
}
}
$parentIds = array( -1000 ); // List of id's to lookup
$rootBom = new Bom( -1000, 'Root' );
$bomsById[ -1000 ][] = $rootBom; // For each id there can be multiple instances if we want separate instances of Bom for each time it occurs in the tree
$maxLevel = 0;
while ( count( $parentIds ) > 0
&& $maxLevel++ < 10
&& $result = $mysqli->query( "SELECT * FROM categories WHERE parent_id IN ( " . implode( ", ", $parentIds ) . " ) ORDER BY name" ) )
{
$parentIds = array(); // Clear the lookup list
$newBomsById = array();
while ( $row = $result->fetch_assoc() )
{
$boms = $bomsById[ $row[ 'parent_id' ] ];
if ( $boms )
{
foreach ( $boms as $bomToUpdate )
{
$compontentBom = new Bom( $row[ 'id' ], $row[ 'name' ] );
$bomToUpdate->Components[] = $compontentBom;
$newBomsById[ $compontentBom->Id ][] = $compontentBom;
}
$parentIds[] = $row[ 'id' ]; // Build new list of id's to lookup
}
}
$bomsById = $newBomsById;
}
echo '<!--
' . print_r( $rootBom, true ) . '
-->';

How to find Level of element from array

I have an array which has id and parent_id like this
$element=new array(
[0]=>array(1,0), --------level 1
[1]=>array(2,0), -------level 1
[2]=>array(3,1), ------level 2
[3]=>array(4,1), ------level 2
[4]=>array(5,1), ------level 2
[5]=>array(6,2), ------level 2
[6]=>array(7,3), ------level 3
[7]=>array(8,2), ------level 2
[8]=>array(9,3), ------level 3
[9]=>array(10,6), ------level 3
[10]=>array(11,6), ------level 3
);
this is my array, in inner array first element is id of the array and second element is id of parent element.
now i want to find level of each element from root.
assume zero (0) is root element.
You can use a recursive approach. I'm assuming that the item at index N in the outer array always has id N+1. If not, you'll first have to search for the item with the matching id, but otherwise the rest of the logic should be the same.
<?php
function findLevel($id) {
$item = $element[$id-1]; //If my assumption (above) is incorrect,
// you'll need to replace this with an appropriate
// search function, which could be as simple as
// a loop through the array.
$parent = $item[1];
if ($parent == 0) {
//Parent is root. Assuming root is level 0, then
// this item is level 1.
return 1;
}
return 1 + findLevel($parent);
?>
$parent_id = n;
$level = 0;
while($parent_id != 0){
$inner_array = $element[$parent_id];
$parent_id = $inner_array[1];
$level ++;
}
Let's try this, you initially set $parent_id to the index of the $element array you want to know the level.
Make sure each level can be calculated

Building a specific array from a table of data

I have a table of data as such:
id | item | parent_id
1 item 1 0
2 item 2 0
3 item 3 2
4 item 4 3
5 item 5 1
...
The id is autoincrementing, and the parent_id reflects the id on the left. You may have come accross a database table design like this before.
The parent_id is not sequential as you can see.
I need to get this table data into an array in the format where all parents become a potential heading with their children underneath.
So I am looking at a structure like this:
Item 1
Item 5
Item 2
Item 3
Item 4
etc
In PHP I need an array structure that can display the above. But I am having a serious brain fart!
Can anyone help me with the array structure?
you may write somethin like this:
$a = Array(item1 => Array(item5), item2 => Array(item3 => Array(item4)))
or
$a = Array(item1 => parentid, item2 => parentid2 ....)
in the first example one item is the key for all ist children stored in the Array, in the other example all items are stored using an item key and an int value. this int value is the key for the parent item. which method you use depends on what you want to do with this Array. maybe both of my ideas are not good enough for your Needs. if thats the case, tell me what you need.
First of all, i will suggest you to read this, it's very useful for hierarchical structured data and there are available queries which will help you to get parents, children, etc ... and so and so.
Now to answer your question, try this :
$result = array();
while($data = mysql_fetch_assoc($query)) {
$id = $data['id'];
$parent = $data['parent_id'];
$keys = array_keys($result);
if(in_array($parent, $keys)) {
$result[$parent] = array_merge($result[$parent], array($id => $data['item']));
} else {
$result = array_merge($result, array($id => $data['item']));
}
}

Array key exists in multidimensional array

I'm trying to arrange a group of pages in to an array and place them depending on their parent id number. If the parent id is 0 I would like it to be placed in the array as an array like so...
$get_pages = 'DATABASE QUERY'
$sorted = array()
foreach($get_pages as $k => $obj) {
if(!$obj->parent_id) {
$sorted[$obj->parent_id] = array();
}
}
But if the parent id is set I'd like to place it in to the relevant array, again as an array like so...
$get_pages = 'DATABASE QUERY'
$sorted = array()
foreach($get_pages as $k => $obj) {
if(!$obj->parent_id) {
$sorted[$obj->id] = array();
} else if($obj->parent_id) {
$sorted[$obj->parent_id][$obj->id] = array();
}
}
This is where I begin to have a problem. If I have a 3rd element that needs to be inserted to the 2nd dimension of an array, or even a 4th element that needs inserting in the 3rd dimension I have no way of checking if that array key exists. So what I can't figure out is how to detect if an array key exists after the 1st dimension and if it does where it is so I can place the new element.
Here is an example of my Database Table
id page_name parent_id
1 Products 0
2 Chairs 1
3 Tables 1
4 Green Chairs 2
5 Large Green Chair 4
6 About Us 0
Here is an example of the output I'd like to get, if there is a better way to do this I'm open for suggestions.
Array([1]=>Array([2] => Array([4] => Array([5] => Array())), [3] => Array()), 6 => Array())
Thanks in advanced!
Well, essentially you are building a tree so one of the ways to go is with recursion:
// This function takes an array for a certain level and inserts all of the
// child nodes into it (then going to build each child node as a parent for
// its respective children):
function addChildren( &$get_pages, &$parentArr, $parentId = 0 )
{
foreach ( $get_pages as $page )
{
// Is the current node a child of the parent we are currently populating?
if ( $page->parent_id == $parentId )
{
// Is there an array for the current parent?
if ( !isset( $parentArr[ $page->id ] ) )
{
// Nop, create one so the current parent's children can
// be inserted into it.
$parentArr[ $page->id ] = array();
}
// Call the function from within itself to populate the next level
// in the array:
addChildren( $get_pages, $parentArr[ $page->id ], $page->id );
}
}
}
$result = array();
addChildren( $get_pages, $result );
print_r($result);
This is not the most efficient way to go but for a small number of pages & hierarchies you should be fine.

Google analytics api. To sort result while in the loop

i need help with google analytics gapi class with php. (http://code.google.com/p/gapi-google-analytics-php-interface)
I want to output how many times each item in catalog was viewed. The page for the item generates with id for example:
/item.php?id=1
/item.php?id=2
ect.
So everything goes right with my code until i want to order by the most viewed item, since i am using loop, to generate random filters:
$filter = "pagePath == /item.php?id=".$i++."";
I am not able to use sort in gapi "requestReportData".
With the code shown below everyting outputs right, but i don't know how to sort everything so it will shown results from the most viewed item till least.
The code:
$ga = new gapi(ga_email,ga_password);
$dimensions = array('pagePath');
$metrics = array('pageviews');
$termFrom = 2011-06-01;
$termUntil = 2011-06-30;
echo '<strong>ITEMS VIEW STATISTIC</strong><br />';
for ( $i='1'; $i<'20';)
{
$filter = "pagePath == /item.php?id=".$i++."";
$ga->requestReportData(table_id,$dimensions,$metrics,'-pageviews',$filter, $termFrom, $termUntil);
foreach($ga->getResults() as $result)
{ $j= $i-1; $b=$j-1;
echo $z++.') Items which ID is:'.$j++.' and NAME is: '.$ItemsNamesArray[$b]['item_name'].' was viewed: '.$result->getpageviews() . ' times<br />';
}
}
It outputs:
ITEMS VIEW STATISTIC
1) Items which ID is:1 and NAME is:
Book was viewed: 9 times
2) Items which ID is:2 and NAME is:
Box: 1 times
3) Items which ID is:3 and NAME is:
Table: 3 times
4) Items which ID is:4 and NAME is:
House: 27 times
I want it to output:
ITEMS VIEW STATISTIC
1) Items which ID is:4 and NAME is:
House was viewed: 27 times
2) Items which ID is:1 and NAME is:
Book was viewed: 9 times
3) Items which ID is:3 and NAME is:
Table was viewed: 3 times
4) Items which ID is:2 and NAME is:
Box was viewed: 1 times
You can use regular expressions for filters to get all your twenty items at once and have Google Analytics sort them:
$ga = new gapi(ga_email,ga_password);
$dimensions = array('pagePath');
$metrics = array('pageviews');
$termFrom = '2011-06-01';
$termUntil = '2011-06-30';
$filter = 'pagePath=~/item\.php\?id=[0-9]*' // Matches all item URLs
$sort = '-pageviews'; // Sorted by desc. pageview count
$maxResults = 20; // First 20 entries
$ga->requestReportData(table_id, $dimensions, $metrics, $sort, $filter, $termFrom, $termUntil, 1, $maxResults);
foreach($ga->getResults as $i => $result){
// Do your magic for each item
}
This is untested, the regular expression in the filter should match correctly, though.
I assumed you want the twenty most-viewed item URLs.

Categories