In PHP, hierarchical structure array based on randomised relational data? - php

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()

Related

How to display unique results from an array with two conditions

In $data I have the following values:
Result
[{"id":2,"author":"example#mail.com","id_orders":502},
{"id":2,"author":"example#mail.com","id_orders":503},
{"id":2,"author":"example#mail.com","id_orders":505},
{"id":3,"author":"second-example#mail.com","id_orders":502},
{"id":3,"author":"second-example#mail.com","id_orders":503},
{"id":3,"author":"second-example#mail.com","id_orders":505},
{"id":4,"author":"third-example#mail.com","id_orders":502},
{"id":4,"author":"third-example#mail.com","id_orders":503},
{"id":4,"author":"third-example#mail.com","id_orders":505}]
I want unique results for id and id_orders. I want 3 out of these 9 results. I have tried this, but it helps on one id_orders condition.
PHP code
$result = json_decode($data, true);
$unique_array = [];
foreach($result as $element) {
$hash = $element['id_orders'];
$unique_array[$hash] = $element;
}
$data = array_values($unique_array);
Do you know how it can be different to make it work for two?
You can do it by keeping track of the values that were already used. Disclaimer: this solution will only produce a clear result for cases where the number of unique values for both criteria is the same.
$uniqueArray = [];
$usedValues = [
'id' => [],
'id_orders' => [],
];
foreach ($result as $element) {
if (!in_array($element['id'], $usedValues['id']) && !in_array($element['id_orders'], $usedValues['id_orders'])) {
$uniqueArray[] = $element;
$usedValues['id'][] = $element['id'];
$usedValues['id_orders'][] = $element['id_orders'];
}
}
Basically, what's happening here is that we're using $usedValues to store all the unique values we've already used and comparing against it using in_array. When we iterate through the objects, any object with an id or id_orders that has already been used will be skipped. The pairings will be done in order of appearance in the array.
I've gone an extra mile to try and make this code a bit more generic:
* Finds elements with a unique combination of values under given keys.
*
* Assumes all elements in the array are arrays themselves and that the
* subarrays have the same structure. Assumes subarray elements are not
* objects (uses strict comparison).
*/
function uniqueCombination(array $arrayToFilter, array $keysToFilterOn): array
{
if (empty($arrayToFilter) || empty($keysToFilterOn)) {
throw new \InvalidArgumentException(
'Parameters of uniqueCombination must not be empty arrays'
);
}
// get the keys from the first element; others are assumed to be the same
$keysOfElements = array_keys(reset($arrayToFilter));
$keysPresentInBoth = array_intersect($keysToFilterOn, $keysOfElements);
// no point in running the algorithm if none of the keys are
// actually found in our array elements
if (empty($keysPresentInBoth)) {
return [];
}
$result = [];
$usedValues = array_combine(
$keysPresentInBoth,
array_fill(0, count($keysPresentInBoth), [])
);
foreach ($arrayToFilter as $element) {
if (!isAlreadyUsed($usedValues, $element)) {
$result[] = $element;
foreach ($keysPresentInBoth as $keyToUse) {
$usedValues[$keyToUse][] = $element[$keyToUse];
}
}
}
return $result;
}
function isAlreadyUsed(array $usedValues, array $element): bool
{
foreach ($usedValues as $usedKey => $usedValue) {
if (in_array($element[$usedKey], $usedValue)) {
return true;
}
}
return false;
}
In its core, this is the same algorithm, but made dynamic. It allows a variable number of keys to filter on (that's why they're passed separately as an argument), so the $usedValues array is created dynamically (using the first element's keys as its own keys, filled with empty arrays) and all the keys must be compared in loops (hence the separate function to check if an element's value had already been used).
It could probably be tweaked here or there as I haven't tested it thoroughly, but should provide satisfactory results for most structures.

PHP code performance optimization help needed

I have the following code, which checks is an element exists, and if it exists, it checks for the same name, with an incremented number at the end.
For example, it checks is the key "test" exists in the array $this->elements, and if it exists, it checks for "test2", and so on, until the key doesn't exist.
My original code is:
if (isset($this->elements[$desired])) {
$inc = 0;
do {
$inc++;
$new_desired = $desired . $inc;
} while (isset($this->elements[$new_desired]));
$desired = $new_desired;
}
I tried with:
if (isset($this->elements[$desired])) {
return $this->generateUniqueElement($desired, $postfix);
}
private function generateUniqueElement($desired, $postfix) {
$new_desired = $desired . $postfix;
return isset($this->elements[$new_desired]) ? $this->generateUniqueElement($desired, ++$postfix) : $new_desired;
}
But in my tests there's no speed improvement.
Any idea how can I improve the code? On all the pages, this code is called over 10 000 times. And sometimes even over 100k times.
Anticipated thanks!
Without further knowledge on how you generate this list, here's an idea:
$highestElementIds = [];
foreach($this->elements as $element) {
preg_match('/(.*?)(\d+)/', $element, $matches);
$text = $matches[1];
$id = (int)$matches[2];
if(!isset($highestElementIds[$text])) {
$highestElementIds[$text] = $id;
} else {
if($id > $highestElementIds[$text]) {
$highestElementIds[$text] = $id;
}
}
}
// find some element by a simple array access
$highestElementIds['test']; // will return 2 in your example
If your code is really being called 100k times, it should be a lot faster to iterate your list only once and then get the highest id directly from an array which contains the highest number (since you don't need to iterate through it again).
That being said, I still wonder what's the actual reason for having such a huge array in the first place...
Typical unique IDs are either random (UUID or random chars) or sequential numbers. The latter is as simple as it gets and it can be generated with a simple counter:
function generateNewElement($postfix) {
static $i = 0;
return sprintf('%d%s', $i++, $postfix);
}
echo generateNewElement('foo'), PHP_EOL;
echo generateNewElement('foo'), PHP_EOL;
echo generateNewElement('foo'), PHP_EOL;
echo generateNewElement('foo'), PHP_EOL;
0foo
1foo
2foo
3foo
Of course this is just a generic solution so it may not fit your specific use case.

Traversing B-Tree Structure Algorithm

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.

fetch data from model that is called in loop

I have a controller function in CodeIgniter that looks like this:
$perm = $this->job_m->getIdByGroup();
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result = $this->job_m->getDatapermission($pId);
}
$data['permission'] = $result;
What I need to do is list the data in the result in the view, but I get only the last value while using this method. How can I pass all the results to the view?
Store it in an array. Like this:
foreach($perm as $pe=>$p){
$result[] = $this->job_m->getDatapermission($p['id']);
}
Because $result is not an array...
try this:
$result=array();
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result[] = $this->job_m->getDatapermission($pId);
}
$data['permission'] = $result;
Note:
My answer uses a counter to enable the display of a single group result when needed.
Guessing from your need to loop and display the value of $result, possibly, it is an array or object returned by $query->result(). Things could be a bit complex.
Example: if $perm is an array of 5 items( or groups), the counter assigns keys 1 - 5 instead of 0 - 4 as would [] which could be misleading. Using the first view example, you could choose to display a single group value if you wants by passing it via a url segment. Making the code more flexible and reusable. E.g. You want to show just returns for group 2, in my example, $result[2] would do just that else next code runs. See my comments in the code.
$perm = $this->job_m->getIdByGroup();
$counter = 1;
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result[$counter] = $this->job_m->getDatapermission($pId);
$counter++;
}
$data['permission'] = $result;
As mentioned above Note:
I Added a Counter or Key so you target specific level. If the groups are:
Men, Women, Boys, Girls, Children; you'd know women is group two(2) If you desire to display values for just that group, you don't need to rewrite the code below. Just pass the group key would be as easy as telling it by their sequence. To display all the loop without restrictions, use the second view example. To use both, use an if statement for that.
###To access it you could target a specific level like
if(isset($permission)){
foreach($permission[2] as $key => $value){
echo $value->columnname;
}
###To get all results:
foreach($permission as $array){
foreach($array as $key => $value){
echo $value->columnname;
}
}
}

Comparing 2 XML Files using PHP

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;
}

Categories