sorry about the funky title, I was having trouble coming up with one. First off, I managed to actually solve my problem (after tearing apart my code), but I have no idea WHY my solution worked and I am trying to treat this as a learning experience.
I have a class which collects info from the db. This class tracks an index value so that the calling function can grab the results one at a time. So my class code originally contained...
protected $index = NULL;
protected $array = array(stuff);
public function next_item()
{
if($this->index == NULL)
{//first time called
$this->index = 0;
}
else
{
$this->index++;
}
if($this->index < sizeof($this->array))
{
return TRUE;
}
return FALSE;
}
public function get_result()
{
return $this->array[$this->index];
}
And the calling function has something akin to
while($myclass->next_item()){
echo $myclass->get_result();
}
It's all a lot more involved than this of course, but this is the problem code (and you get the idea). This code induced an infinite loop (or the appearance of one) in my project. I fixed my problem by initializing $index to -1, and removing the conditional check for null. So it's now ...
protected $index = -1;
public function next_item()
{
$this->index++;
if($this->index < sizeof($this->array))
{
return TRUE;
}
return FALSE;
}
My question is, WHY did that work? The index was never referenced in the array while it was NULL, I checked. It was always after the conditional check for NULL was called and the assignment to 0 occurred.
I really appreciate your time, in advance. Just trying to turn this into a learning experience, and it's better if I understand why it worked, not just that it did.
I'm sorry if this question is answered somewhere else, I couldn't figure out how to label my question (as you can see) much less have success finding a previous answer to it.
cheers,
-David
Your problem was caused by the fact that 0 == NULL in PHP. So when you set the index to 0 once you would keep setting it to 0 again over and over every time you call next_item, this->index == NULL would always be true.
Changing your test to this->index === NULL would have also solved your problem. See this question which covers the difference between == and === in PHP.
Related
This function works fine outside of a class. Ie simply define the function and call it. Yet when I add it to a class it no longer works - any help is greatly appreciated:
public function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value || (is_array($value) && $this->recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
obviously the $this is removed when not in a class.
Edit:
The error I an getting when using it in a class is:
Invalid argument supplied for foreach()
app\components\GenFun::recursive_array_search('9377907', 9378390)
My sole expectation from the function is that it returns any key (ie identifies that the needle exists in the haystack) - I actually dont care about the actual index.
To be perfectly honest, "it no longer works" isn't a helpful metric by which to assist you in debugging your problem. Nor is "it works fine", since that doesn't tell us your definition of what works means to you. More precisely, these statements don't tell us what you expected the code to do that it's not doing, or what the code is doing that you did not expect.
To me this code is doing exactly what you've told it to do and the result of both a function as well as a class method (using the same code) are identical... See the working 3v4l pastebin here.
However, my guess is that your expectations may be different from what this code actually does. Specifically, this function will return at the very first match of the $needle in the $haystack. Such that the following array, returns 0 (_that is with a needle of 'foo').
$haystack = ['foo', ['foo', 'bar']];
It will also return only the key of the outer-most array in the $haystack. Meaning, the following array returns 0 as the key. Even though the actual match is in $haystack[0][1][2]
$haystack = [['bar',['quix','baz','foo'],'baz'],'quix'];
So depending on what you expected (the inner-most key, or the outer-most key), you may believe this function doesn't work.
So you'll need to clarify exactly what you want the code to do and provide some reproducible example of what didn't work (and that includes the data used or arguments provided to the function).
EDIT:
Hey, I'm glad you figured it out. Here are just a few suggestions to maybe help you refactor this code slightly as well...
So since you're looking for the existence of the needle in any part of the array and don't actually care about the key, you may want to make your intent more obvious in the logic.
So for example, always return a boolean (true on success and false on failure) rather than return false on failure and the key on success. This makes checking the function's result easier and clearer from the caller's perspective. Also, consider naming the function to describe it's intent more clearly (for example: in_array_recursive rather than recursive_array_search since we're not actually intent on searching the array for something, but proving that something is actually in the array). Finally, consider avoiding multiple return points in the same function as this makes debugging harder.
So a cleaner way to write the same code might be something like this:
public function in_array_recursive($needle, $haystack, $strict = false) {
$result = false;
foreach($haystack as $value) {
if(!is_array($value)) {
$result = $strict ? $needle === $value : $needle == $value;
} else {
$result = $this->in_array_recursive($needle, $value, $strict);
}
if ($result) {
break;
}
}
return $result;
}
Now the caller simply does...
$arr = ['bar',['foo']];
if (in_array_recursive('foo', $arr)) {
/* 'foo' is in $arr! */
} else {
/* 'foo' is not in $arr... */
}
Making the code more readable and easier to debug. Notice you also don't have to use exact match if you wanted to add an optional argument for $strict at the end of the function there and also be more inline with in_array.
So the reason this was not working was due to the method by which I was defining $needle.
In my old code it would be input as an integer and in my new code it was a string. The === operator then obviously denied it as being the same. This is why you don't work at 2am :)
I'm trying to edit my co-worker's code (he's on vacation and out of reach) and there is this if statement:
if($this->carrier() == 1 and $this->carrier() == 2) {
return 'V';
}
My hope is that he accidentally put "and" instead of "or" which could explain a bug I'm getting, but he knows much more about PHP than I do and I want to be sure that this is wrong instead of just counter intuitive.
Yes, since it's a function with potential side effects, it might be true.
For example:
function carrier() {
return $someValue++;
}
Yes. The carrier() method could increment whatever value it returns each time you call it.
There's a small chance it could, yes.
He's calling a function twice, and you've not included the text of that function. So that could be doing something we can't see, like counting the number of times it's been called by this process.
On the other hand, it's much more likely that it is indeed a typo.
It is possible. Here is a working example you can run.
class program
{
private $i = 1;
function carrier()
{
$this->i=$this->i+1;
return $this->i-1;
}
function run()
{
if ($this->carrier()==1 && $this->carrier()==2)
{
echo "works";
}
else
{
echo "doesnt work" . $i;
}
}
}
$prog = new Program();
$prog->run();
I just saw this source code on a website, but I don't know what it means, can anyone tell me what it is? thank you so much.
private function buildCache()
{
!empty($this->cache_list) && $this->cache->loadCache($this->cache_list);
}
It is the example of bad code which is hard to support.
The !empty($this->cache_list) && $this->cache->loadCache($this->cache_list); statement is equivalent to $dummy = !empty($this->cache_list) && $this->cache->loadCache($this->cache_list);.
There is such thing as lazy evaluation, so that in A && B, B will be evaluated only is A is true (otherwise A && B is knowingly false and there is no need to evaluate B). Basically, $x = a() && b() is the same as
$x = true;
if(!a()) {
$x = false;
} else {
$x = b();
}
Thus, we can expand the original statement as
$dummy = true;
if(empty($this->cache_list)) {
$dummy = false;
} else {
$dummy = $this->cache->loadCache($this->cache_list);
}
which, remembering that we don't need the $dummy variable, is the same as
if(!empty($this->cache_list)) {
$this->cache->loadCache($this->cache_list);
}
Despite this code is 2 lines longer than the original one, it is much easier to understand and to mantain. You should write the code which is like this final version and avoid writing anything like the original one-liner.
You can see it by yourself: while it was hard for you to tell what is going on in the original one-liner (so hard that you had to ask the question on SO), it is quite easy to see what is going on in the final version: if the cache_list is not empty, we're calling loadCache passing cache_list to it as the argument (otherwise, if the cache_list would be empty, it would probably be pointless to call loadCache passing empty value to it as the argument).
It means if $this->cache_list is not empty and $this->cache->loadCache() function returns true
I guess that's a shortcut for:
private function buildCache()
{
if( ! empty($this->cache_list)){
$this->cache->loadCache($this->cache_list);
}
}
If there is a 'cache_list', it loads it.
You have to check the class or framework documentation for more info on these actions.
out of curiosity I'm wondering if there's a more elegant way to write the conditionals below? I can't see a shorter way of writing it but it feels pretty clunky, so any suggestions welcome!
// Check whether this page has any visuals
if (count($this->page->pagevisuals->find_all()) > 0)
{
// Ok to go ahead and assign
$visual = $this->page->pagevisuals->find_all();
}
// If this is a sub page, parent page may have visuals we can use
elseif (count($this->page->parent->pagevisuals->find_all()) > 0)
{
$visual = $this->page->parent->pagevisuals->find_all();
}
// If two levels deep, grandparent page might have visuals
elseif (count($this->page->parent->parent->pagevisuals->find_all()) > 0)
{
$visual = $this->page->parent->parent->pagevisuals->find_all();
}
You can write a loop instead:
$page = $this->page;
$visual = null;
while (!$visual && $page) {
$visual = $page->pagevisuals->find_all();
$page = $page->parent;
}
I believe this is equivalent, and will work no matter how many levels of parents/nesting you have.
You could assign $this->page to a variable and begin the statements with that, for a very slight improvement.
Alternatively, you could create nested ternary statements to assign $visual, but that's certainly not recommended practice.
A recursive approach:
function getVisuals($root) {
$visuals = $root->pagevisuals->find_all();
if(count($visuals) === 0 && isset($root->parent)) {
$visuals = getVisuals($root->parent);
}
return $visuals;
}
$visuals = getVisuals($this->page);
If you have control over whatever class $this->page is an instance of, then you can make it an instance method.
You could make a recursive method to get rid of those nasty conditionals. Also you're calling the find_all() method twice for every conditional branch which doubles the process time.
Here's an attempt at a recursive function (might not work though, recursive functions are always a bit tricky!). Beware of infinite loops.
<?php
$visual = $this->page->find_all_visuals();
class Page {
function find_all_visuals()
{
$found = $this->pagevisuals->find_all();
if (count($found) > 0) {
return $found;
} else if ($this->parent == null) {
return null;
} else {
return $this->parent->find_all_visuals();
}
}
}
?>
You might want make two changes in your code:
Ensure that getVisuals() returns an empty array instead of null in case there are no visuals
Consider making a null-object - a singleton page instance that has no visuals and has itself as a parent. It might have a method like isNull() so you can easily test if a given page is the null page.
If you make the two adjustments, most of the code concerning visuals will become easier to write and debug.
Getting all the visuals for two levels (I assume you don't want recursion):
$visuals = array_merge(
$this->page->pagevisuals->find_all(),
$this->page->parent->pagevisuals->find_all(),
$this->page->parent->parent->pagevisuals->find_all(),
);
Getting the visuals of the page OR of parent OR of grand parent:
($visuals = $this->page->pagevisuals->find_all()) ||
($visuals = $this->page->parent->pagevisuals->find_all()) ||
($visuals = $this->page->parent->parent->pagevisuals->find_all());
Recursive functions would be much simpler too (this is a method to add to the page object):
public function findRecursive(){
$my_visuals = $this->pagevisuals->find_all()
return $this->parent->isNull()?
$my_visuals
: array_merge($my_visuals, $this->parent->findRecursive());
}
$visual = $this->page->pagevisuals->find_all()
or $visual = $this->page->parent->pagevisuals->find_all()
or $visual = $this->page->parent->parent->pagevisuals->find_all();
What do you do if none of them match? In this code it will be set to the last one, which is not the same as what you did. (In your code $visual was not touched if none matched, in this code it will be set to zero. You could add or $visual = -1 or something similar.)
You can make a loop if you want to avoid all the ->parent, but you'll need some terminator.
$el = $this->page;
while(!$visual = $el->pagevisuals->find_all()) {
$el = $el->parent;
}
This could run forever if it never matches, but I don't know enough about your application to suggest a termination condition - you could add a counter, or something else.
I have this function that I wrote that is abysmally slow since php does not handle recursion well. I am trying to convert it to a while loop, but am having trouble wrapping my head around how to do it.
Can anyone give me some tips?
public function findRoute($curLoc, $distanceSoFar, $expectedValue) {
$this->locationsVisited[$curLoc] = true;
$expectedValue += $this->locationsArray[$curLoc]*$distanceSoFar;
$at_end = true;
for($i = 1; $i < $this->numLocations; $i++) {
if($this->locationsVisited[$i] == false) {
$at_end = false;
if($expectedValue < $this->bestEV)
$this->findRoute($i, $distanceSoFar + $this->distanceArray[$curLoc][$i], $expectedValue);
}
}
$this->locationsVisited[$curLoc] = false;
if($at_end) {
if($expectedValue < $this->bestEV) {
$this->bestEV = $expectedValue;
}
}
}
I'm not going to convert your code, but you can convert a recusive function into an iterative one by creating a stack:
$stack= array();
And instead of invoking $this->findroute(), push your parameters onto this stack:
$stack[] = array($i, $distanceSoFar + $this->distanceArray[$curLoc][$i], $expectedValue);
Now surround basically everything in your function into a while loop draining the stack after having primed it:
while ($stack) {
// Do stuff you already do in your function here
You can convert a recursive function into an iterative function by using a stack to store the current state. Look into array_push() and array_pop().
At a glance I don't think recursion is your problem, yes it's slow in PHP, but It looks like your going over values more than you need to, putting the values in a stack, or several stacks and handling them, may be nice.
custom sort functions have always helped me with problems of this sort.
function sort_by_visited($x,$y)
{
return ($this->locationsVisited[$x] > $this->locationsVisited[$y]) ? -1 : 1;
}
uasort($locationsVisited,'sort_by_visited');
This will prioritize all not visited locations at the top of the stack.
This looks like your trying to find the optimal route for traversal of a series of nodes in a graph.
I'm guessing that you've not studied Computer Science as the "Travelling Salesman" problem is an archetype of Artificial Intelligence. Of course, as such, it has its own Wikipedia page:
http://en.wikipedia.org/wiki/Travelling_salesman_problem
Sorry - but just swapping from a recursive to an iterative function isn't going to make it go any faster ("php does not handle recursion well." - can you provide reference for this assertion). If you need a faster solution then you'll need to look at non-exhaustive/fuzzy methods.
C.