0

I'm trying out my first recursive function (at least I think I am!) and it only half works. First, the code:

function check_title($i,$title) {
    $q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
    $num=mysql_num_rows($q);
    if($num==0) {
            return $title;
    }else {
            $title=$title.' ('.$i++.')';
            check_title($i,$title);
    }
}

What I'm doing is taking a string (title) and checking if that title exists in the db already. If it does, I want to append a number to the newer of the duplicates (e.g. 'I Am A Title' becomes 'I Am A Title-2'). I then need to run the function again to check this new version of my title, and increase the appended value as required ('I Am A Title-3'). Once no duplication is discovered, return the Title in its acceptable form.

It works when no duplication is found (the easy bit), but fails when duplication is found. Instead of appending a number, the entire title variable is emptied.

Any help would by greatly appreciated!

6
  • 1
    This is a completely unnecessary use of recursion, and could just as easily accomplished with a basic loop. Commented Jan 16, 2012 at 16:41
  • Put return $title in the else block too, as thats where the call is made from. Commented Jan 16, 2012 at 16:42
  • It is? I thought that anything that would have to run for an indefinate number of cycles was ideal for a recursive function - am I wrong? Or how is this case unsuitable? Commented Jan 16, 2012 at 16:43
  • You also need to change $i++ to ++$i (pre-increment instead of post-increment) Commented Jan 16, 2012 at 16:44
  • @Eamonn Recursion has a few very specific use cases, tree traversal is a big one... a while loop is a basic loop that is ideal for a loop containing an undefined amount of iterations. Commented Jan 16, 2012 at 16:47

5 Answers 5

2

As Mchl stated, the empty title is due to a lack of return in the else branch.

However, there is a problem with the function as it does not do what you intend. Currently, your function is building $title as 'Title-1-2-3-4-etc' the way you currently append the number to the title and check again. Instead of passing a modified title on the recursed call you should just pass the base title. Then, for the query, modify the title.

function check_title($title, $i = 0) {
    $qtitle = $title . ($i == 0 ? '' : "-$i");
    $q=mysql_query("SELECT Title FROM posts WHERE Title = '$qtitle'");
    $num=mysql_num_rows($q);
    if($num==0) {
        return $title . ($i == 0 ? '' : "-$i");
    }else {
        return check_title(++$i,$title);
    }
}

PS, I also changed the order of parameters that way your initial call doesn't need to specify 0.

$title = check_title($title);

PPS, I should mention this is a solution to do it via recursion. However, a recursive solution is not the proper solution here as it needlessly makes return trips to the DB. Instead, you should use an sql query that selects all titles LIKE "$title%" Order by title asc. Then, iterate through each result and do a regex comparison with the title to see if it matches a pattern <title>|<title>-<#>. If it does you increment a duplicate counter. At the end you spit out the title with an appended counter value. I'll leave that solution as an exercise for the original poster.

Sign up to request clarification or add additional context in comments.

1 Comment

I'm going with the loop as suggessted by jondavidjohn as being the best solution to my problem, but I appreciate seeing a working function as I was aiming for originally. I'll study this and see how these things are properly formulated, thanks a bunch!
1

Use a loop instead...

$record_exists = true;
$title_base = "I Am A Title";
$title = $title_base;
$i = 0;
while($record_exists) {
    $q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
    $num=mysql_num_rows($q);
    if($num==0) {
        $record_exists = false;
        // Exit the loop.
    }
    else {
        $i++;
        $title = $title_base . "-" . $i;
    }
}

echo $title; // last existing title

However, optimally you'd do more work with a single SQL query and iterate the result, saving a lot of trips to and from the database.

And just for fun...

$title_base = "I Am A Title";
$title = $title_base;
for ($i=1, $num=1; $num != 0; $i++)
{
    $q=mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
    $num=mysql_num_rows($q);
    $title = $title_base . "-" . $i;
}

echo $title; // next title in sequence (doesn't yet exist in the db)

12 Comments

Fantastic, that's very helpful. But I'm curious now as to why everyone is saying recursion is not the answer - why wouldn't one use a recursive function here? Is it just a case of the simpler solution being the best?
Yes. Occam's Razor is your friend in this field.
Worked a treat, thanks again! Guess I was trying to be too clever for my own good :)
You're not alone, employing cleverness is satisfying, unfortunately it's not always to best solution.
@Eamonn: in procedural/object oriented language like PHP, recursion is rarely the only possible solution, while at the same time it introduces a risk of (gasp!) stack overflow. Generally it's good to avoid it, unless you really know you want it. On the other hand in functional languges like OCaml, recursion is one of the most basic tools, while usage of loops is very much limited.
|
1
  1. You lack a return in else branch.
  2. Recursion is not the best idea for this application. Hint: do a query like this SELECT MAX(Title) FROM posts WHERE Title LIKE '$title%');

1 Comment

Your hint is bad, MAX() is not the right way to do this, e.g. 'A title-9' and 'A title-10'. 9 will win.
1

Your recursive function is fine except for 2 things:

  1. The original title isn't maintained between recursive calls. Hence each time $title = $title . ' (' . $i++ . ')' runs, another parenthesis is appended to the title, like "abc", "abc (1)", "abc (1) (2)" and so on.

  2. You are returning $title when no more matches are found but no title is returned in the ELSE. It is important to do so. When the execution reaches the IF, it returns the title but the returned title is not assigned anywhere and hence is lost.

Here is the revised code:

$orgTitle = 'I am a title';
function check_title($i, $title = '') {
    global $orgTitle;
    $q = mysql_query("SELECT Title FROM posts WHERE Title = '$title'");
    $num = mysql_num_rows($q);
    if ($num == 0) {
        return $title;
    } else {
        $title = $orgTitle . ' (' . ++$i .')';
        return check_title($i, $title);
    }
}
echo check_title(0, $orgTitle);

Note the addition of new variable $orgTitle. I've replaced it in the assignment statement inside the ELSE. This does the fix for point 1 above.

Also note the return added before check_title call in the ELSE. This solves point 2.

Hope it makes sense!

Add-on: Recursions are confusing, logically complex and tricky to debug. Also, recursive calls consume more memory (not in case of simple operations like your example) because the compiler/interpreter had to maintain the state variables for all steps in a recursion.

1 Comment

Upvoted for another example of a proper recursive function :)
-1

In order to minimise MySql interactions I'd recommend something similar to the following.

function checkTitle($title)
{
    /*Return all iterations of the title*/
    $res = mysql_query("SELECT COUNT(title) AS titleCount FROM posts 
                    WHERE SUBSTR(title, 1,". strlen($title) .") = '$title' ");
    /*Return the incremented title*/
    return $title. (mysql_result($res, 0, "titleCount")  + 1);

}

Example:

mysql> select title from posts;
+----------+
| title    |
+----------+
| firefox1 |
| firefox2 |
| shoe     |
| firefox3 |
+----------+
4 rows in set (0.00 sec)

mysql> SELECT COUNT(title) AS titleCount FROM posts WHERE SUBSTR(title, 1,7) = 'firefox' ;
+------------+
| titleCount |
+------------+
|          3 |
+------------+
1 row in set (0.00 sec)
mysql>

---- Follow up test

Test table structure.
mysql>SHOW COLUMNS FROM posts;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| title | varchar(12) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

/*Test code and output*/
function checkTitle($title)
{
    /*Return all iterations of the title*/
    $res = mysql_query("SELECT COUNT(title) AS titleCount FROM posts 
                    WHERE SUBSTR(title, 1,". strlen($title) .") = '$title' ");
    /*Return the incremented title*/
    return $title. (mysql_result($res, 0, "titleCount")  + 1);

}
mysql_connect("localhost","root", "password");
mysql_select_db("test");
echo checkTitle("firefox");

Output: firefox4

9 Comments

Its throwing 'not a valid result resource...' errors? Column and var names are correct...
Sorry my mistake, I never bracketed up the decrement in SUBSTR() which probably passed an int instead of a string to mysql_query(). I've updated the code, please try it and let me know how you get on - ideally posting mysql_error() in response if it doesn't work.
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '0,3)' at line 2
Sorry third time lucky: Missing comma in the SUBSTR() call, as I've commented I'm not in a position to construct a table and test the queries.
Hmm, bear with me twenty minutes in the name of good practice I'll commandeer something with MySql on it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.