concat tree hierarchie in a recursive PHP function - php

I have a user table with hierachical users. So users can have a parent user. I am trying to return an array of all child user ids of a certain user.
My function returns "null". What's wrong?
public function userDownline($userid, $result = array()) {
$dbconn = $this->DBase();
$children = $dbconn->GetAll('SELECT id FROM users WHERE parent=' . (int)$userid);
if(count($children) > 0) {
foreach($children As $k=>$v) {
if(!in_array($v['id'], $result)) $result[] = $v['id'];
$this->userDownline($v['id'], $result);
}
} else {
return $result;
}
}

Of course it will return null, because you are in block if(count($children)) and there is no return from this.
I think you have to do something like this:
<?php
public function userDownline($userid, &$result = array())
{
$dbconn = $this->DBase();
$children = $dbconn->GetAll('SELECT id FROM users WHERE parent=' . (int)$userid);
if (count($children) > 0) {
foreach ($children As $k => $v) {
if (!in_array($v['id'], $result)) $result[] = $v['id'];
$this->userDownline($v['id'], $result);
}
}
return $result;
}
I added reference in function signature and move return out of the conditional block.
But this is really totaly inefficient way and dangerous(because of - out of memory, too many nesting level and other exceptions).
There are 2 better ways:
Use https://neo4j.com/ - Graph Database - best option for your task.
If you still wanna use only Sql DB - read about Nested set Model http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

Related

PHP mysql Tree Child count and List All Child node level wise

I have a tree like
and I want the output for requeted ID for example Admin
my table structure is
I have a method that returns Child count level wise but I also want to return the child list level-wise with child count level-wise like 2nd image output required.
function childCountLevelWise($conn,$ID, $level){
if ($level>14){
$count = array(0=>0);
return $count;
}
$sql="select * from user_my_tree t where t.parent_ID=".$ID;
$result=returnResults($conn,$sql);
if ($result==null){
$count = array(0=>0);
}
else{
$count = array(0=>0);
foreach($result as $key=>$row)
{
$count[0]++;
$children=childCountLevelWise($conn,$row['ID'], $level+1);
$index=1;
foreach ($children as $child)
{
if ($child==0)
continue;
if (isset($count[$index]))
$count[$index] += $child;
else
$count[$index] = $child;
$index++;
}
}
}
return $count;
}
It sounds like you just want a way of counting nested sets, I can reproduce your example without using a database with this code:
function returnResults($parent)
{
switch ($parent) {
case 'root':
return ['vijay', 'suresh', 'mukesh'];
case 'vijay':
return ['manish', 'rohan', 'manu'];
case 'manish':
return ['rinku', 'raja', 'vijay2'];
default:
return [];
}
}
function childCountLevelWise($parent, $level, &$result)
{
$result[$level] = empty($result[$level]) ? [] : $result[$level]; // init array
if ($level > 14) {
return; // ignore levels over 14
}
$levelResults = returnResults($parent); // get results for this parent
$result[$level] = array_merge($result[$level], $levelResults); // add to results for this level
foreach ($levelResults as $child) {
childCountLevelWise($child, $level + 1, $result); // check for each child at this level
}
}
And calling it and printing the results with this code
childCountLevelWise('root', 0, $result);
// print result
foreach ($result as $level => $people) {
if (!empty($people)) {
printf('Result for level %d: %s', $level, implode(',', $people));
echo "\n";
}
}
Will result in:
Result for level 0: vijay,suresh,mukesh
Result for level 1: manish,rohan,manu
Result for level 2: rinku,raja,vijay2
From there I think it should be simple enough to modify the returnResults function in my example to query the database, although if you're using this on a lot of results you might want to consider the performance costs of this. There are good solutions to having tree structures in the database already, such as the Nested Set in Doctrine.

Count all values in nested arrays using recursion in PHP

I want to learn how to use recursive functions, so I started to create one:
<?php
$tableau = [[[[2],[2]],[[2],[2]]],[[[2],[2]],[[2],[2]]]];
function test_count_recursive($tab, $compte = 0){
foreach($tab as $ss_tab){
if(!is_array($ss_tab)){
$compte += 1;
}
else{
test_count_recursive($ss_tab, $compte);
}
}
return $compte;
}
echo test_count_recursive($tableau);
But it doesn't work, can you tell me why?
Generally, you pass a piece of data up the recursive call tree or down, not both. In this case, you don't need to pass parent values down. Just return the count and let the parent node accumulate it:
<?php
function test_count_recursive($tab /* one param only */) {
$compte = 0; // <-- store the local total for this node
foreach ($tab as $ss_tab) {
if (is_array($ss_tab)) {
$compte += test_count_recursive($ss_tab /* one param only */);
// ^^^^^^^ accumulate total from children
}
else {
$compte++;
}
}
return $compte;
}
$tableau = [[[[2],[2]],[[2],[2]]],[[[2],[2]],[[2],[2]]]];
echo test_count_recursive($tableau); // => 8

How to recursively build a list of Item Substitutions when multiple substitutes exist

In Microsoft Dynamics Nav 2013, there is a feature for specifying Item Substitutions for an item (product); However, you can specify more than one substitution for a single product and technically a substitution can itself have one or more substitutions.
I am trying to build a recursive solution in PHP that allows me to take a known product code, and recursively search for item substitutions to generate a one dimensional array of items. If this were a one to one relationship (parent,child) this would be a trivial task for me, but the fact that there can be multiple childs on any given iteration is kind of blowing my mind.
My question is if anyone knows how to write a recursive method for the situation I've described above? Below I will layout the way the data is structured to give a better understanding of the problem:
$lineItems = array(
'XXX-0',
'XXX-1',
'XXX-3'
);
$substitutionsLookup = array(
0 => array('No_' => 'XXX-1', 'Substitute No_' => 'XXX-2'),
1 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-4'),
2 => array('No_' => 'XXX-3', 'Substitute No_' => 'XXX-5'),
3 => array('No_' => 'XXX-5', 'Substitute No_' => 'XXX-6')
);
// Resulting product code substitutions for XXX-0
$result1 = array();
// Resulting product code substitutions for XXX-1
$result2 = array('XXX-2');
// Resulting product code substitutions for XXX-3
$result3 = array('XXX-4', 'XXX-6');
Edit (added my attempt to solve using recursive method):
protected function getSubstitutions($haystack, $needle, &$results = array())
{
if (count($haystack) == 0)
{
return false;
}
$matches = array();
foreach ($haystack as $index => $check)
{
if ($check['No_'] === $needle)
{
$newHaystack = $haystack;
unset($newHaystack[$index]);
$moreMatches = $this->getSubstitutions($newHaystack, $check['Substitute No_'], $results);
if ($moreMatches === false)
{
$matches[] = $check['Substitute No_'];
}
}
}
if (count($matches))
{
foreach($matches as $match)
{
$results[] = $match;
}
}
return $results;
}
Edit (Final code used, derived from accepted answer):
class ItemSubstitutionService implements ServiceLocatorAwareInterface
{
public function getSubstitutions($itemNo, $noInventoryFilter = true, $recursive = true)
{
$substitutions = array();
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($itemNo);
if ($recursive)
{
foreach($directSubs as $sub)
{
$this->getSubstitutionsRecursive($sub, $substitutions);
}
} else {
$substitutions = $directSubs;
}
foreach($substitutions as $index => $sub)
{
$inventory = $this->itemLedgerEntryTable->getQuantityOnHand($sub->getSubstituteNo());
$sub->setInventory($inventory);
if ($noInventoryFilter)
{
if ($inventory == 0)
{
unset($substitutions[$index]);
}
}
}
return $substitutions;
}
private function getSubstitutionsRecursive(ItemSubstitution $sub, &$subs)
{
$directSubs = $this->itemSubstitutionTable->getSubstitutionsByNo($sub->getSubstituteNo());
if (empty($directSubs))
{
$subs[$sub->getSubstituteNo()] = $sub;
}
foreach($directSubs as $curSub)
{
$this->getSubstitutionsRecursive($curSub, $subs);
}
}
}
This code could serve as a solution for your example.
I just presume that you fetch the list of 'direct' items substitutes from your database, so you could replace GetDirectSubstitutes with the code that fetches the substitutes list for the given item (I used you example array as a data source).
Just be careful - this simple implementation doesn't check for cyclical references. If your initial data contains loops, this code will stuck.
function GetDirectSubstitutes($itemNo)
{
global $substitutionsLookup;
$items = array();
foreach ($substitutionsLookup as $itemPair) {
if ($itemPair['No'] == $itemNo) {
array_push($items, $itemPair['SubstNo']);
}
}
return $items;
}
function GetSubstitutesTree($itemNo, &$substitutes)
{
$directSubst = GetDirectSubstitutes($itemNo);
if (!empty($directSubst)) {
$substitutes = array_merge($substitutes, $directSubst);
foreach ($directSubst as $item) {
GetSubstitutesTree($item, $substitutes);
}
}
}

php foreach on array when arrays might be nested

The following code uses foreach on an array and if the value is an array it does a for each on the nested array
foreach ($playfull as $a)
{
if (is_array($a))
{
foreach ($a as $b)
{
print($b);
print("<p>");
}
} else {
print($a);
print("<p>");
}
}
This only works if you know that the arrays may only be nested one level deep
If arrays could be nested an unknown number of levels deep how do you achieve the same result? (The desired result being to print the value of every key in every array no matter how deeply nested they are)
You can use array_walk_recursive. Example:
array_walk_recursive($array, function (&$val)
{
print($val);
}
This function is a PHP built in function and it is short.
Use recursive functions (that are functions calling themselves):
function print_array_recursively($a)
{
foreach ($a as $el)
{
if (is_array($el))
{
print_array_recursively($el);
}
else
{
print($el);
}
}
}
This is the way, print_r could do it (see comments).
You want to use recursion, you want to call your printing function in itself, whenever you find an array, click here to see an example
$myArray = array(
"foo",
"bar",
"children" => array(
"biz",
"baz"),
"grandchildren" => array(
"bang" => array(
"pow",
"wow")));
function print_array($playfull)
{
foreach ($playfull as $a)
{
if (is_array($a))
{
print_array($a);
} else {
echo $a;
echo "<p>";
}
}
}
echo "Print Array\n";
print_array($myArray);
You could use a recursive function, but the max depth will be determined by the maximum nesting limit (see this SO question, Increasing nesting functions calls limit, for details about increasing that if you need it)
Here's an example:
$array = array(1,array(2,3,array(4,5)),6,7,8);
function printArray($item)
{
foreach ($item as $a)
{
if (is_array($a))
{
printArray($a);
} else {
print($a);
print("<p>");
}
}
}
printArray($array);
I hope that helps.
Try this -
function array_iterate($arr, $level=0, $maxLevel=0)
{
if (is_array($arr))
{
// unnecessary for this conditional to enclose
// the foreach loop
if ($maxLevel < ++$level)
{ $maxLevel = $level; }
foreach($arr AS $k => $v)
{
// for this to work, the result must be stored
// back into $maxLevel
// FOR TESTING ONLY:
echo("<br>|k=$k|v=$v|level=$level|maxLevel=$maxLevel|");
$maxLevel= array_iterate($v, $level, $maxLevel);
}
$level--;
}
// the conditional that was here caused all kinds
// of problems. so i got rid of it
return($maxLevel);
}
$array[] = 'hi';
$array[] = 'there';
$array[] = 'how';
$array['blobone'][] = 'how';
$array['blobone'][] = 'are';
$array['blobone'][] = 'you';
$array[] = 'this';
$array['this'][] = 'is';
$array['this']['is'][] = 'five';
$array['this']['is']['five'][] = 'levels';
$array['this']['is']['five']['levels'] = 'deep';
$array[] = 'the';
$array[] = 'here';
$var = array_iterate($array);
echo("<br><br><pre>$var");

PHP arrays - a 'set where key=?' type function?

Is there a built in php function that allows me to set a value of an array based on a matching key? Maybe i've been writing too much SQL lately, but i wish I could perform the following logic without writing out nested foreach array like the following:
foreach($array1 AS $k1 => $a1) {
foreach($array2 AS $a2) {
if($a1['id'] == $a2['id']) {
$array[$k1]['new_key'] = $a2['value'];
}
}
}
Is there a better way to do this? In SQL logic, it would be "SET array1.new_key = x WHERE array1.id = array2.id". Again, i've been writing too much SQL lately :S
When I need to do this, I use a function to first map the values of one array by id:
function convertArrayToMap(&$list, $attribute='id') {
$result = array();
foreach ($list as &$item) {
if (is_array($item) && array_key_exists($attribute, $item)) {
$result[$item[$attribute]] = &$item;
}
}
return $result;
}
$map = convertArrayToMap($array1);
Then iterate through the other array and assign the values:
foreach ($array2 AS $a2) {
$id = $a2['id'];
$map[$id]['new_key'] = $a2['value'];
}
This are less loops overall even for one pass, and it's convenient for further operations in the future.
This one is fine and correct
foreach(&$array1 AS &$a1) {
foreach($array2 AS $a2) {
if($a1['id'] == $a2['id']) {
$a1['new_key'] = $a2['value'];
}
}
}

Categories