Issue with PHP recursive function - php

I need to traverse the following type of structures:
P
/ | \
E1 E2 E3 .....
/ \ / \ |
V1 V2 V1 V2 V3 .....
| | | | / \
T1 T2 T3 T4 T5 T6 .....
In order to form an associative array with the following elements:
V1(key) = [T1(E1), T3(E2), ...]
V2(key) = [T2(E1), T4(E2), ...]
V3(key) = [T5(E3), T6(E3), ...]
.....
Now here comes the tricky part: the structure is actually simplified. I don't know beforehand how many E-level nodes I'll actually need to deal with (3 in the drawing), or how many V-level nodes each of them has (but at least 1 will be there), and furthermore, each V-level node may also have multiple T-nodes.
I tried using a recursion function to do this (in PHP). I'll simplify the code because it has weird methods regarding some objects that don't really matter to the discussion. My current attempt results in:
V1(key) = [T1(E1)]
V2(key) = [T2(E1)]
That I think means that the traversal is only occurring going down the first E-level "branch".
This is my code:
$result = [];
$traverser = function($node) use (&$traverser, &$result) {
$children = $node->getChildrenArray();
foreach($children as $key=>$child){
if ($child->nodeType() == 'v_node') {
$v_node_key = $child->name;
$t_nodes = $child->getChildrenArray();
if ( !array_key_exists($v_node_key, $results) ){
$results[$v_node_key] = [];
}
foreach($t_nodes as $keyt=>$t_node) {
$info_array = $t_node->toArray();
array_push($results[$v_node_key], $info_array);
}
} else if ($child->nodeType() == 'e_node') {
// keep digging
return $traverser($child);
}
}
};
$traverser($p_node);
I think the problem is that once I call the $traverser function within the foreach it won't come back and resume from the previous state.
Can anyone advise on how I should be tackling this to get the result I placed above?

Well, this is a bit awkward and I'm still not entirely sure if this is the right motive, but I solved this by removing the return in my code.
I thought that the return would allow me to exit the nested function call, but rather I think it jumped out of the first function call (the $traverser($p_node); line).
Even so, by changing the return $traverser($child); line to $traverser($child); it did what it had to do.
Hope this helps anyone!

I not show about what error you got but i suggest you to change you function to be this
function traverser($results, $node) {
$children = $node->getChildrenArray();
foreach($children as $key=>$child){
if ($child->nodeType() == 'v_node') {
$v_node_key = $child->name;
$t_nodes = $child->getChildrenArray();
if ( !array_key_exists($v_node_key, $results) ){
$results[$v_node_key] = [];
}
foreach($t_nodes as $keyt=>$t_node) {
$info_array = $t_node->toArray();
array_push($results[$v_node_key], $info_array);
}
} else if ($child->nodeType() == 'e_node') {
// keep digging
return traverser($child);
}
}
return $results;
}
Hope this help

Well, since you know that you'll have P->E->V->T-nodes, you could simply go for multiple foreach-loops, like this
foreach($p_node->getChildren() as $e_node) {
$e_node_key = $e_node->name;
foreach($e_node->getChildren() as $v_node) {
$v_node_key = $v_node->name;
foreach($v_node->getChildren() as $t_node) {
$t_node_key = $t_node->name;
// do whatever it needs to array_push to results
}
}
}

Related

PHP Algorithm Performance

I am looking for ways to improve the performance of this simple PHP function.
The issue is I am pulling data into an array from an API which unfortunately does not provide much scope for filtering. As a result, the dataset is initially quite large, before I loop through and unset() the key/value pairs I have no use for in my application.
Is there a better way to do this? Load time is currently 2000ms.
$callBokun = new callBokun($this->_op, $this->_currentDate);
$callBokun->setResponse("POST", "/booking.json/product-booking-search", "https://api.bokun.is/booking.json/product-booking-search");
$s = $callBokun->getResponse();
unset($s["tookInMillis"], $s["totalHits"], $s["totalHits"], $s["query"]);
$unset_loop = count($s["results"]);
while ($unset_loop > 0) {
--$unset_loop;
unset($s["results"][$unset_loop]["id"], $s["results"][$unset_loop]["externalBookingReference"], $s["results"][$unset_loop]["parentBookingId"], $s["results"][$unset_loop]["vendor"]["id"], $s["results"][$unset_loop]["vendor"]["flags"], $s["results"][$unset_loop]["productCategory"], $s["results"][$unset_loop]["productExternalId"], $s["results"][$unset_loop]["resold"], $s["results"][$unset_loop]["seller"], $s["results"][$unset_loop]["hasNotes"], $s["results"][$unset_loop]["assignedResources"], $s["results"][$unset_loop]["channel"], $s["results"][$unset_loop]["agent"], $s["results"][$unset_loop]["affiliate"], $s["results"][$unset_loop]["vessel"], $s["results"][$unset_loop]["harbour"], $s["results"][$unset_loop]["saleSource"], $s["results"][$unset_loop]["extranetUser"], $s["results"][$unset_loop]["agentUser"], $s["results"][$unset_loop]["cancellationDate"], $s["results"][$unset_loop]["cancelledBy"], $s["results"][$unset_loop]["cancelNote"], $s["results"][$unset_loop]["endDate"], $s["results"][$unset_loop]["customer"]["contactDetailsHidden"], $s["results"][$unset_loop]["customer"]["contactDetailsHiddenUntil"], $s["results"][$unset_loop]["customer"]["phoneNumberCountryCode"], $s["results"][$unset_loop]["customer"]["country"], $s["results"][$unset_loop]["customer"]["id"], $s["results"][$unset_loop]["customer"]["language"], $s["results"][$unset_loop]["customer"]["nationality"], $s["results"][$unset_loop]["customer"]["created"], $s["results"][$unset_loop]["customer"]["uuid"], $s["results"][$unset_loop]["customer"]["sex"], $s["results"][$unset_loop]["customer"]["dateOfBirth"], $s["results"][$unset_loop]["customer"]["address"], $s["results"][$unset_loop]["customer"]["postCode"], $s["results"][$unset_loop]["customer"]["state"], $s["results"][$unset_loop]["customer"]["place"], $s["results"][$unset_loop]["customer"]["organization"], $s["results"][$unset_loop]["customer"]["passportId"], $s["results"][$unset_loop]["customer"]["passportExpMonth"], $s["results"][$unset_loop]["customer"]["passportExpYear"], $s["results"][$unset_loop]["customer"]["credentials"], $s["results"][$unset_loop]["creationDate"], $s["results"][$unset_loop]["totalPrice"], $s["results"][$unset_loop]["productType"], $s["results"][$unset_loop]["customerInvoice"], $s["results"][$unset_loop]["resellerInvoice"], $s["results"][$unset_loop]["currency"], $s["results"][$unset_loop]["prepaid"], $s["results"][$unset_loop]["paidAmount"], $s["results"][$unset_loop]["resellerPaidType"], $s["results"][$unset_loop]["discountPercentage"], $s["results"][$unset_loop]["discountAmount"], $s["results"][$unset_loop]["unconfirmedPayments"], $s["results"][$unset_loop]["sellerCommission"], $s["results"][$unset_loop]["affiliateCommission"], $s["results"][$unset_loop]["agentCommission"], $s["results"][$unset_loop]["notes"], $s["results"][$unset_loop]["fields"]["pickupPlaceRoomNumber"], $s["results"][$unset_loop]["fields"]["pickupPlaceDescription"], $s["results"][$unset_loop]["fields"]["customizedPrice"], $s["results"][$unset_loop]["fields"]["dropoff"], $s["results"][$unset_loop]["fields"]["pickup"], $s["results"][$unset_loop]["fields"]["flexible"], $s["results"][$unset_loop]["fields"]["partnerBookings"], $s["results"][$unset_loop]["fields"]["startTimeId"], $s["results"][$unset_loop]["fields"]["startHour"], $s["results"][$unset_loop]["fields"]["selectedFlexDayOption"], $s["results"][$unset_loop]["fields"]["dropoffPlaceDescription"], $s["results"][$unset_loop]["fields"]["comboParent"], $s["results"][$unset_loop]["fields"]["startMinute"], $s["results"][$unset_loop]["fields"]["priceCategoryBookings"], $s["results"][$unset_loop]["boxBooking"], $s["results"][$unset_loop]["boxProduct"], $s["results"][$unset_loop]["boxSupplier"], $s["results"][$unset_loop]["contactDetailsHidden"], $s["results"][$unset_loop]["contactDetailsHiddenUntil"]);
}
$this->_bookingInfo = $s;
Thanks in advance!
It is probably more efficient to build a new array with the properties you do want to keep. unset is a costly operation.
Here is the pattern you could use if you want to keep a field with name booking1 (just add any other field in the same way):
$s = $callBokun->getResponse();
foreach($s["results"] as $row) {
$result[] = [
"booking1" => $row["booking1"],
// assign any other fields you want to keep in the same way
// ...
];
}
$this->_bookingInfo = $result;
Alternative 1
An alternative way is with array_map:
$this->_bookingInfo = array_map(function ($row) {
return [
"booking1" => $row["booking1"],
// assign any other fields you want to keep in the same way
// ...
];
}, $s['result']);
Alternative 2
You could also try with array_intersect_key:
$filter = array_flip([
'booking1',
// list all your desired fields here
]);
foreach($s["results"] as $row) {
$result[] = array_intersect_key($row, $filter);
}
$this->_bookingInfo = $result;
You can of course combine the two alternatives.

Using R, how to reference variable variables (or variables variable) a la PHP [revisited]

In a previous Using R, how to reference variable variables (or variables variable) a la PHP[post]
I asked a question about something in R analagous to PHP $$ function:
Using R stats, I want to access a variable variable scenario similar to PHP double-dollar-sign technique: http://php.net/manual/en/language.variables.variable.php
Specifically, I am looking for a function in R that is equivalent to $$ in PHP.
The get( response works for strings (characters).
lapply is a way to loop over lists
Or I can loop over and get the values ...
for(name in names(vars))
{
val = vars[[name]];
I still haven't had the $$ function in R answered, although the lapply solved what I needed in the moment.
`$$` <- function
that allows any variable type to be evaluated. That is still the question.
UPDATES
> mlist = list('four'="score", 'seven'="years");
> str = 'mlist$four'
> mlist
$four
[1] "score"
$seven
[1] "years"
> str
[1] "mlist$four"
> get(str)
Error in get(str) : object 'mlist$four' not found
> mlist$four
[1] "score"
Or how about attributes for an object such as mobj#index
UPDATES #2
So let's put specific context on the need. I was hacking the texreg package to build a custom latex output of 24 models of regression for a research paper. I am using plm fixed effects, and the default output of texreg uses dcolumns to center, which I don't like (I prefer r#{}l, so I wanted to write my own template. The purpose for me, to code this, is for me to write extensible code that I can use again and again. I can rebuild my 24 tables across 4 pages in seconds, so if the data change, or if I want to tweak the function, I immediately have a nice answer. The power of abstraction.
As I hacked this, I wanted to get more than the number of observations, but also the number of groups, which can be any user defined index. In my case it is "country" (wait for it, hence, the need for variable variables).
If I do a lookup of the structure, what I want is right there: model$model#index$country which would be nice to simply call as $$('model$model#index$country'); where I can easily build the string using paste. Nope, this is my workaround.
getIndexCount = function(model,key="country")
{
myA = attr(summary(model)$model,"index");
for(i in 1:length(colnames(myA)))
{
if(colnames(myA)[i] == key) {idx = i; break;}
}
if(!is.na(idx))
{
length(unique(myA[,idx]));
} else {
FALSE;
}
}
UPDATES #3
Using R, on the command line, I can type in a string and it gets evaluated. Why can't that internal function be directly accessed, and the element captured that then gets printed to the screen?
There is no equivalent function in R. get() works for all types, not just strings.
Here is what I came up with, after chatting with the R-bug group, and getting some ideas from them. KUDOS!
`$$` <- function(str)
{
E = unlist( strsplit(as.character(str),"[#]") );
k = length(E);
if(k==1)
{
eval(parse(text=str));
} else {
# k = 2
nstr = paste("attributes(",E[1],")",sep="");
nstr = paste(nstr,'$',E[2],sep="");
if(k>2) {
for(i in 3:k)
{
nstr = paste("attributes(",nstr,")",sep="");
nstr = paste(nstr,'$',E[i],sep="");
}
}
`$$`(nstr);
}
}
Below are some example use cases, where I can directly access what the str(obj) is providing... Extending the utility of the '$' operator by also allowing '#' for attributes.
model = list("four" = "score", "seven"="years");
str = 'model$four';
result = `$$`(str);
print(result);
matrix = matrix(rnorm(1000), ncol=25);
str='matrix[1:5,8:10]';
result = `$$`(str);
print(result);
## Annette Dobson (1990) "An Introduction to Generalized Linear Models".
## Page 9: Plant Weight Data.
ctl <- c(4.17,5.58,5.18,6.11,4.50,4.61,5.17,4.53,5.33,5.14);
trt <- c(4.81,4.17,4.41,3.59,5.87,3.83,6.03,4.89,4.32,4.69);
group <- gl(2, 10, 20, labels = c("Ctl","Trt"));
weight <- c(ctl, trt);
lm.D9 <- lm(weight ~ group);
lm.D90 <- lm(weight ~ group - 1); # omitting intercept
myA = anova(lm.D9); myA; str(myA);
str = 'myA#heading';
result = `$$`(str);
print(result);
myS = summary(lm.D90); myS; str(myS);
str = 'myS$terms#factors';
result = `$$`(str);
print(result);
str = 'myS$terms#factors#dimnames';
result = `$$`(str);
print(result);
str = 'myS$terms#dataClasses#names';
result = `$$`(str);
print(result);
After realizing the back-tick can be a bit tedious, I chose to update the function, calling it access
access <- function(str)
{
E = unlist( strsplit(as.character(str),"[#]") );
k = length(E);
if(k==1)
{
eval(parse(text=str));
} else {
# k = 2
nstr = paste("attributes(",E[1],")",sep="");
nstr = paste(nstr,'$',E[2],sep="");
if(k>2) {
for(i in 3:k)
{
nstr = paste("attributes(",nstr,")",sep="");
nstr = paste(nstr,'$',E[i],sep="");
}
}
access(nstr);
}
}

PHP Function with parameters to return different strings

**
After the first answer: 5 year old project that needs to be updated
My problem is that at the moment one single product-download page has at least 14 functions and over 1200 lines of code. I basically just want to make it more simple to make changes and decrease the file size and code e.g. add all vars at the top of the page and not having to search thru the entire code to find where they have to be added and cut out as many dupicate functions as possible ...
**
Some of the vars (all with extra functions) for the same object:
$p1_files = "'p-1.zip', 'p-2.zip', 'p-3.zip', 'p-4.zip', 'p-5.zip'";
$p2_files = "'p2-1.zip', 'p2-2.zip', 'p2-3.zip', 'p2-4.zip', 'p2-5.zip'";
$p3_files = "'p3-1.zip', 'p3-download-2.zip', 'p3-download-3.zip', 'p3-4.zip', 'p3-5.zip'";
the "very shortend" function:
function p_files_function(){
$p1_files = "'p-1.zip', 'p-2.zip', 'p-3.zip', 'p-4.zip', 'p-5.zip'";
return $p1_files;
}
the "p_files_function()" returns
'p-1.zip', 'p-2.zip', 'p-3.zip', 'p-4.zip', 'p-5.zip'
which is perfect and I can use to create the listings but it only works with 1 var.
Now, what I would like to do is to add all vars to one function and only return the needed one
Something like
// List all vars at top of page
$p1_files = "'p-1.zip', 'p-2.zip', 'p-3.zip', 'p-4.zip', 'p-5.zip'";
$p2_files = "'p2-1.zip', 'p2-2.zip', 'p2-3.zip', 'p2-4.zip', 'p2-5.zip'";
$p3_files = "'p3-1.zip', 'p3-download-2.zip', 'p3-download-3.zip', 'p3-4.zip', 'p3-5.zip'";
// one function to get the needed vars
function p_files_function($args){
if Value1 {
$needed_files = $p1_files
}
if Value2 {
$needed_files = $p2_files
}
if Value3 {
$needed_files = $p3_files
}
return $needed_files;
}
// easy way to get the needed vars
p_files_function(Value2) //should retuns $p2_files
any ideas?
This is very shortend there are also images, documents and so on, I managed to cut everything else down to minimum but with this I am lost, all I need is a starting point.
Thanks
Personally I think adding the suffix _function to your function name is a little redundant. That said:
function p_files($type) {
switch ($type) {
case 'p1':
return "'p-1.zip', 'p-2.zip', 'p-3.zip', 'p-4.zip', 'p-5.zip'";
case 'p2':
return "'p2-1.zip', 'p2-2.zip', 'p2-3.zip', 'p2-4.zip', 'p2-5.zip'";
case 'p3':
return "'p3-1.zip', 'p3-download-2.zip', 'p3-download-3.zip', 'p3-4.zip', 'p3-5.zip'";
default:
return ''; // or trigger error or whatever
}
}
$needed_files = p_files('p1'); // etc

Evaluate logic expression given as a string in PHP

I have an object which has a state property, for example state = 'state4' or state = 'state2'.
Now I also have an array of all available states that the state property can get, state1 to state8 (note: the states are not named stateN. They have eight different names, like payment or canceled. I just put stateN to describe the problem).
In addition to that, I have a logical expression like $expression = !state1||state4&&(!state2||state5) for example. This is the code for the above description:
$state = 'state4';
$expression = '!state1||state4&&(!state2||state5)';
Now I want to check if the logical expression is true or false. In the above case, it's true. In the following case it would be false:
$state = 'state1';
$expression = state4&&!state2||(!state1||state7);
How could this be solved in an elegant way?
//Initialize
$state = 'state4';
$expression = '!state1||state4&&(!state2||state5)';
//Adapt to your needs
$pattern='/state\d/';
//Replace
$e=str_replace($state,'true',$expression);
while (preg_match_all($pattern,$e,$matches)
$e=str_replace($matches[0],'false',$e);
//Eval
eval("\$result=$e;");
echo $result;
Edit:
Your update to the OQ necessitates some minor work:
//Initialize
$state = 'payed';
$expression = '!payed||cancelled&&(!whatever||shipped)';
//Adapt to your needs
$possiblestates=array(
'payed',
'cancelled',
'shipped',
'whatever'
);
//Replace
$e=str_replace($state,'true',$expression);
$e=str_replace($possiblestates,'false',$e);
//Eval
eval("\$result=$e;");
echo $result;
Edit 2
There has been concern about eval and PHP injection in the comments: The expression and the replacements are completly controlled by the application, no user input involved. As long as this holds, eval is safe.
I am using ExpressionLanguage, but there are few different solutions
ExpressionLanguage Symfony Component - https://symfony.com/doc/current/components/expression_language.html
cons - weird array syntax - array['key']. array.key works only for objects
cons - generate notice for array['key'] when key is not defined
pros - stable and well maintainer
https://github.com/mossadal/math-parser
https://github.com/optimistex/math-expression
Please remember that eval is NOT an option, under NO circumstances. We don't live an a static world. Any software always grows and evolves. What was once considered a safe input an one point may turn completely unsafe and uncontrolled.
I think you have a case which can be solved if you model each of your expressions as a rooted directed acyclic graph (DAG).
I assumed acyclic since your ultimate aim is to find the result of boolean algebra operations (if cycling occur in any of graph, then it'd be nonsense I think).
However, even if your graph structure—meaningfully—can be cyclic then your target search term will be cyclic graph, and it should still have a solution.
$expression = '!state1||state4&&(!state2||state5)';
And you have one root with two sub_DAGs in your example.
EXPRESSION as a Rooted DAG:
EXPRESSION
|
AND
___/ \___
OR OR
/ \ / \
! S_1 S_4 ! S_2 S5
Your adjacency list is:
expression_adj_list = [
expression => [ subExp_1, subExp_2 ] ,
subExp_1 => [ ! S_1, S_4 ],
subExp_2 => [ ! S_2, S5 ]
]
Now you can walk through this graph by BFS (breadth-first search algorithm) or DFS (depth-first search algorithm) or your custom, adjusted algorithm.
Of course you can just visit the adjacency list with keys and values as many times as you need if this suits and is easier for you.
You'll need a lookup table to teach your algorithm that. For example,
S2 && S5 = 1,
S1 or S4 = 0,
S3 && S7 = -1 (to throw an exception maybe)
After all, the algorithm below can solve your expression's result.
$adj_list = convert_expression_to_adj_list();
// can also be assigned by a function.
// root is the only node which has no incoming-edge in $adj_list.
$root = 'EXPRESSION';
q[] = $root; //queue to have expression & subexpressions
$results = [];
while ( ! empty(q)) {
$current = array_shift($q);
if ( ! in_array($current, $results)) {
if (isset($adj_list[$current])) { // if has children (sub/expression)
$children = $adj_list[$current];
// true if all children are states. false if any child is subexpression.
$bool = is_calculateable($children);
if ($bool) {
$results[$current] = calc($children);
}
else {
array_unshift($q, $current);
}
foreach ($children as $child) {
if (is_subexpresssion($child) && ! in_array($child, $results)) {
array_unshift($q, $child);
}
}
}
}
}
return $results[$root];
This approach has a great advantage also: if you save the results of the expressions in your database, if an expression is a child of the root expression then you won't need to recalculate it, just use the result from the database for the child subexpressions. In this way, you always have a two-level depth DAG (root and its children).

Calculating similarity -> python code to php code - Whats wrong?

I am trying to convert the following python code to a PHP code. Can you please explain me what is wrong in my PHP code, because I do not get the same results. If you need example data please let me know.
# Returns a distance-based similarity score for person1 and person2
def sim_distance(prefs,person1,person2):
# Get the list of shared_items
si={}
for item in prefs[person1]:
if item in prefs[person2]: si[item]=1
# if they have no ratings in common, return 0
if len(si)==0: return 0
# Add up the squares of all the differences
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2)
for item in prefs[person1] if item in prefs[person2]])
return 1/(1+sum_of_squares)
My PHP code:
$sum = 0.0;
foreach($arr[$person1] as $item => $val)
{
if(array_key_exists($item, $arr[$person2]))
{
$p = sqrt(pow($arr[$person1][$item] - $arr[$person2][$item], 2));
$sum = $sum + $p;
}
}
$sum = 1 / (1 + $sum);
echo $sum;
Thanks for helping!
The main difference is that you've added sqrt to the PHP code. The PHP also doesn't handle the special case of no prefs in common, which gives 0 in the python version and 1 in the PHP version.
I tested both versions and those are the only differences I found.
this is close as i could make a direct translation... (untested)
function sim_distance($prefs, $person1, $person2) {
$si = array();
foreach($prefs[$person1] as $item) {
if($item in $prefs[$person2]) $si[$item]=1;
}
if(count($si)==0) return 0;
$squares = array();
foreach($prefs[$person1] as $item) {
if(array_key_exists($item,$prefs[$person2])) {
$squares[] = pow($prefs[$person1][$item]-$prefs[$person2][$item],2);
}
}
$sum_of_squares = array_sum($squares);
return 1/(1+$sum_of_squares);
}
I don't really know what you're trying to do, or if I've interpreted the indentation correctly...but maybe this'll help. I'm assuming your data structures have the same layout as in the python script.
oh...and i'm interpreting the python as this:
def sim_distance(prefs,person1,person2):
# Get the list of shared_items
si={}
for item in prefs[person1]:
if item in prefs[person2]: si[item]=1
# if they have no ratings in common, return 0
if len(si)==0: return 0
# Add up the squares of all the differences
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2) for item in prefs[person1] if item in prefs[person2]])
return 1/(1+sum_of_squares)

Categories