I'm having a hard time trying to write a traversing function that outputs a child-node's parent-nodes.
Take a look at the example b-tree
Here is the sample dataset I'm using:
$nodes = array(
array('f','b'),
array('f','g'),
array('b','a'),
array('b','d'),
array('g','i'),
array('d','c'),
array('d','e'),
array('i','h')
);
I'm trying to output a results array that contains all child node arrays which contain parent associations.
Example Outputs:
node(d)'s parents are (b,f)
node(c)'s parents are (d,b,f)
node(h)'s parents are (i,g,f)
I can't figure out how to traverse past the direct parent node.
foreach($nodes as $node){
//CHECK IF NODE EXISTS
if(array_key_exists($node[1],$results)){
//DO NOTHING
array_push($results[$node[1]],$node[0]);
}
else{
//CREATE NEW CHILD ARRAY
$results[$node[1]] = [];
//PUSH PARENT INTO CHILD ARRAY
array_push($results[$node[1]],$node[0]);
}
}
foreach($results as $k => $v){
echo "child[$k] parents(" . implode($v,', ').")" ;
echo "</br>";
}
Question: How can I achieve this output in the most efficient manor?
The best way to deal with such cases is to use recursive functions.
echo findParents('h',$nodes);
function findParents($find,$btree){
$parents;
foreach($btree as $node){
if($node[1]===$find){
$parents .=$find.',';
return $parents .= findParents($node[0], $btree);
}
}
return $find;
}
Check the live code here: https://www.tehplayground.com/ElTdtP61DwFT1qIc
The only drawback is that it would return the original node in the return list. But I think you can live with that.
I think the better representation of the tree would be:
$nodes = array(
'f'=>array('d','g'),
'b'=>array('a','d'),
'g'=>array('i'),
'd'=>array('c','e'),
'i'=>array('h')
);
But that would need slight modification to the above code.
To get an array as the response:
function findParentsArray($find,$btree){
$parentsArray = explode(',',findParents($find,$btree));
array_shift($parentsArray);
return $parentsArray;
}
It should be possible to have that done directly in findParents() but I don't have time to look into it now.
Related
I have an array that contains any number of elements, and is allowed to be a multidimensional array, too. My testing example of such array data is:
$arr = array(
array('Material-A', 'Material-B'),
array('Profile-A', 'Profile-B', 'Profile-C'),
array('Thread-A', 'Thread-B'),
// ... any number of elements
);
From this multidimensional array I need to create a single array that is linear in the following format.
$arrFormated = array(
'Material-A',
'Material-A_Profile-A',
'Material-A_Profile-A_Thread-A',
'Material-A_Profile-A_Thread-B',
'Material-A_Profile-A_Thread-C',
'Material-A_Profile-B',
'Material-A_Profile-B_Thread-A',
'Material-A_Profile-B_Thread-B',
'Material-A_Profile-B_Thread-C',
'Material-A_Profile-C',
'Material-A_Profile-C_Thread-A',
'Material-A_Profile-C_Thread-B',
'Material-A_Profile-C_Thread-C',
'Material-B',
'Material-B_Profile-A',
'Material-B_Profile-A_Thread-A'
// Repeat similar pattern found above, etc...
);
For a recursive function, the best that I've been able to come up with thus far is as follows:
private function showAllElements($arr)
{
for($i=0; $i < count($arr); $i++)
{
$element = $arr[$i];
if (gettype($element) == "array") {
$this->showAllElements($element);
} else {
echo $element . "<br />";
}
}
}
However, this code is no where close to producing my desired results. The outcome from the above code is.
Material-A
Material-B
Profile-A
Profile-B
Profile-C
Thread-A
Thread-B
Could somebody please help me with the recursive side of this function so I may get my desired results?
I'd generally recommend thinking about what you want to be recursive. You tried to work with the current element in every recursion step, but your method needs to look at the next array element of the original Array in each recursion step. In this case, it's more useful to pass an index to your recursive function, because the 'current element' (the $arr in showAllElements($arr)) is not helpful.
I think this code should do it:
$exampleArray = array(
array('Material-A', 'Material-B'),
array('Profile-A', 'Profile-B', 'Profile-C'),
array('Thread-A', 'Thread-B','Thread-C'),
// ... any number of elements
);
class StackOverflowQuestion37823464{
public $array;
public function dumpElements($level = 0 /* default parameter: start at first element if no index is given */){
$return=[];
if($level==count($this->array)-1){
$return=$this->array[$level]; /* This is the anchor of the recursion. If the given index is the index of the last array element, no recursion is neccesarry */
}else{
foreach($this->array[$level] as $thislevel) { /* otherwise, every element of the current step will need to be concatenated... */
$return[]=$thislevel;
foreach($this->dumpElements($level+1) as $stringifyIt){ /*...with every string from the next element and following elements*/
$return[]=$thislevel.'_'.$stringifyIt;
}
}
}
return $return;
}
}
$test=new StackOverflowQuestion37823464();
$test->array=$exampleArray;
var_dump($test->dumpElements());
I recently started using JSTREE to display my json data (I am showing it as an array in my sample code here). But, some of the nodes are repeating. I was thinking of adding an If loop to check if the node is already a child node but not sure how to do that. I also think my code is also not very well performing because of the many for loops. I am a PHP newbie so not sure if there might be a faster way to recurse through an array. Here is my code.
$relation = array(
"parent"=>array("item"=>array("cs","ls")),
"cs"=>array("item"=>array("cs_1")),
"ls"=>array("item"=>array("ls_1")),
"cs_1"=>array("item"=>array("cs_1_1"))
);
$tree = array();
$i=0;
foreach($relation as $key_parent=>$value_parent) {
#$children = array_keys($value_parent);
$tree[$i++] = array('id'=>$key_parent,'text'=>$key_parent,'state'=>array('opened'=>true),'parent'=>'#');
foreach($value_parent['item'] as $key_child_1=>$value_child_1) {
$tree[$i++]= array('id'=>$value_child_1,'text'=>$value_child_1,'state'=>array('opened'=>true),'parent'=>$key_parent);
}
}
Your input structure is far from optimal, but it can still work. If I understand correctly, this is what you need:
$relation = array(
"parent"=>array("item"=>array("cs","ls")),
"cs"=>array("item"=>array("cs_1")),
"ls"=>array("item"=>array("ls_1")),
"cs_1"=>array("item"=>array("cs_1_1"))
);
$tree = array();
foreach($relation as $key_parent=>$value_parent) {
$tree[$key_parent] = array('id'=>$key_parent,'text'=>$key_parent,'state'=>array('opened'=>true),'parent'=>'#');
foreach($value_parent['item'] as $key_child_1=>$value_child_1) {
$tree[$value_child_1]= array('id'=>$value_child_1,'text'=>$value_child_1,'state'=>array('opened'=>true),'parent'=>$key_parent);
}
}
$tree = array_values($tree);
The only fix is using the actual node ID as a key in the $tree array, and then calling array_values to convert it back.
My questions is very very similar to the following, but I have a couple of differences, including writing it in PHP.
https://softwareengineering.stackexchange.com/questions/261074/process-to-generate-hierarchical-structure-based-on-relational-data
I have the following data in a CSV:
firstname,surname,position,manager
John,D,Clerk,Jane D
Bob,B,Clerk,Jane D
Harry,S,Supervisor,Jack W
Jack,W,CEO,
David,B,Cook,Jack W
Jane,D,CTO,
Amy,L,CBO,
Mike,M,Singer,John D
Using PHP, I need to create an array of this data in a heirarchical structure in which records sit under the correct manager record. A top level record is indicated by a blank 'manager' field. Notice how there can be more than one top level record. Furthermore, notice how the data is completely random.
I tried to convert the above buildtree() function in PHP, but it doesn't seem correct. Here is my attempt, can someone explain where I might be going wrong? The trouble I seem to get is that when I pass the array to buildtree(), its ending because it starts with the 'blank' key and thus it doesn't continue with the recursion:
function buildtree($parents,$tree = array(),$parent_id = ''){
$parent = $parents[$parent_id];
if($parent == ''){
return $tree;
}
foreach($parent as $child){
$report = array($child[0].' '.$child[1]);
if(empty($tree)){
$tree = $report;
}
else{
$reports = $tree['reports'];
$reports = array_merge($reports,$report);
}
buildtree($parents,$report,$child[0].' '.$child[1]);
}
return $tree;
}
# something not right about this.
# first, build an array of managers
foreach($_SESSION['csv_data'] as $k=>$val){
if($k > 0){
$manager = $val[3];
$heir[$manager]['reports'][$val[0].' '.$val[1]] = '';
}
}
$final = buildtree($heir);
echo '<pre>';
print_r($final);
echo '</pre>';
For reference, here is the original python buildtree() function:
def buildtree(t=None, parent_eid=''):
"""
Given a parents lookup structure, construct
a data hierarchy.
"""
parent = parents.get(parent_eid, None)
if parent is None:
return t
for eid, name, mid in parent:
report = { 'name': name }
if t is None:
t = report
else:
reports = t.setdefault('reports', [])
reports.append(report)
buildtree(report, eid)
return t
data = buildtree()
I want to convert a xml script into a associative array in PHP. The XML script is :
<ages>
<Peter>
<Peterchild>4</Peterchild>
<Peterchild>6</Peterchild>
</Peter>
<Quagmire>30</Quagmire>
<Joe>34</Joe>
</ages>
and the code i wrote for converting it to an array is
${$xml->getName()} = array();
foreach($xml->children() as $child){
$ages[$child->getName()] = (string)$child;
}
which gives the output as
Array
(
[Peter] =>
[Quagmire] => 30
[Joe] => 34
)
The problem is that I am unable to figure out a condition to recursively traverse through the children of children (children of Peter in this example). How to I change this code to consider those nodes as well?
You'll need to create a function that parses one level, then have the function call itself when it finds a node inside the current level. It's pretty complex and can get messy. Out of curiosity, why would you do this when you can access all the information you need in object format using a SimpleXML object?
${$xml->getName()} = array();
foreach($xml->children() as $child)
{
if (count($child->children() > 0)
{
foreach ($child->children as $childnode)
{
$ages[$childnode->getName()] = (string)$childnode;
}
}
else {
$ages[$child->getName()] = (string)$child;
}
}
This will only work if it is 1 level deep, if it is going to be more levels you should write a function, that will cycle through it level by level.
I want to compare 2 big xml files and retrieve the differences. Like ExamXML and DiffDog do. The solution I found was cycling through all child nodes of each file simultaneously and check if they are equal. But I have no idea how to achieve that... How can I loop through all child nodes and their properties? How can I check if the first element of the first file is equal to the first element of the second file, the second element of the first file is equal to the second element of the second file and so on?
Do yo have a better idea to compare 2 xml files?
I was looking for something to compare two XML like you, and I found this solution that works very well.
http://www.jevon.org/wiki/Comparing_Two_SimpleXML_Documents
I hope that helps to someone.
Have you looked at using XPath at all? Seems like an easy way to grab all of the child nodes. Then you'd be able to loop through the nodes and compare the attributes/textContent.
This might be a very alternative solution for you but this is how I would do it.
First, I'd try to get the format into something much more manageable like an array so I would convert the XML to an array.
http://www.bytemycode.com/snippets/snippet/445/
This is some simple code to do just that.
Then PHP has an array_diff() function that can show you the differences.
http://www.php.net/manual/en/function.array-diff.php
This may or may not work for you considering what you need to do with the differences but if you're looking to just identify and act upon them this might be a very quick solution to your problem.
Try the xmldiff extension
http://pecl.php.net/xmldiff
It's based on the same library as the perl module DifferenceMarkup, you'll get a diff XML document and can even merge then.
//Child by Child XML files comparison in PHP
//Returns an array of non matched children in variable &$reasons
$reasons = array();
$xml1 = new SimpleXMLElement(file_get_contents($xmlFile1));
$xml2 = new SimpleXMLElement(file_get_contents($xmlFile2));
$result = XMLFileComparison($xml1, $xml2, $reasons);
/**
* XMLFileComparison
* Discription :- This function compares XML files. Returns array
* of nodes do not match in pass by reference parameter
* #param $xml1 Object Node Object
* #param $xml2 Object Node Object
* #param &$reasons Array pass by reference
* returns array of nodes do not match
* #param $strict_comparison Bool default False
* #return bool <b>TRUE</b> on success or array of strings on failure.
*/
function XMLFileComparison(SimpleXMLElement $xml1, SimpleXMLElement $xml2, &$reasons, $strict_comparison = false)
{
static $str;
// compare text content
if ($strict_comparison) {
if ("$xml1" != "$xml2") return "Values are not equal (strict)";
} else {
if (trim("$xml1") != trim("$xml2"))
{
return " Values are not equal";
}
}
// get all children
$XML1ChildArray = array();
$XML2ChildArray = array();
foreach ($xml1->children() as $b) {
if (!isset($XML1ChildArray[$b->getName()]))
$XML1ChildArray[$b->getName()] = array();
$XML1ChildArray[$b->getName()][] = $b;
}
foreach ($xml2->children() as $b) {
if (!isset($XML2ChildArray[$b->getName()]))
$XML2ChildArray[$b->getName()] = array();
$XML2ChildArray[$b->getName()][] = $b;
}
//print_r($XML1ChildArray);
//print_r($XML2ChildArray);
// cycle over children
if (count($XML1ChildArray) != count($XML2ChildArray)) return "mismatched children count";// Second File has less or more children names (we don't have to search through Second File's children too)
foreach ($XML1ChildArray as $child_name => $children) {
if (!isset($XML2ChildArray[$child_name])) return "Second file does not have child $child_name"; // Second file has none of this child
if (count($XML1ChildArray[$child_name]) != count($XML2ChildArray[$child_name])) return "mismatched $child_name children count"; // Second file has less or more children
print_r($child_name);
foreach ($children as $child) {
// do any of search2 children match?
$found_match = false;
//$reasons = array();
foreach ($XML2ChildArray[$child_name] as $id => $second_child) {
$str = $str.$child_name.($id+1)."/"; // Adding 1 to $id to match with XML data nodes numbers
//print_r($child, $second_child);
// recursive function call until reach to the end of node
if (($r = XMLFileComparison($child, $second_child, $reasons, $strict_comparison)) === true) {
// found a match: delete second
$found_match = true;
unset($XML2ChildArray[$child_name][$id]);
$str = str_replace($child_name.($id+1)."/", "", $str);
break;
}
else {
unset($XML2ChildArray[$child_name][$id]);
$reasons[$str] = $r;
$str = str_replace($child_name.($id+1)."/", "", $str);
break;
}
}
}
}
return True;
}