Рекурсия. Почему?

На днях столкнулся с таким вот противоречием. До сих пор так и не выяснил в чем же проблема. Есть рекурсивная функция, которая выводит полный путь файла из базы данны. То есть все уже давно проиндексировано и занесено в базу со связями файл_id-папка_id. Вот первоначальный код с выводом результата:

 function GetFileRootById($Id, $root = null){
        $res = $this->db->SelectRow(’SELECT dir_name, dir_related_dir ‘ .
                                              ‘FROM dir ‘ .
                                              ‘WHERE dir_id = ?’,
                                              $Id);
        $root = $res[’dir_name’].’/’.$root;
        if ($res[’dir_related_dir’] == 0){
               return $root;
        }
        else{
               $this->GetFileRootById($res[’dir_related_dir’], $root);
        }
 }
$result = GetFileRootById(123);
var_dump($result); // NULL

Вопрос: почему она постоянно возвращает NULL, тогда как написанное ниже возвращает значение? Отличие в том, что результат я передаю по ссылке:

        function GetFileRootById($Id, &$root = null){
               $res = $this->db->SelectRow(’SELECT dir_name, dir_related_dir ‘ .
                                                     ‘FROM dir ‘ .
                                                      ‘WHERE dir_id = ?’,
                                                     $Id);
               $root = $res[’dir_name’].’/’.$root;
               if ($res[’dir_related_dir’] == 0){
                       return;
               }
               else{
                       $this->GetFileRootById($res[’dir_related_dir’], $root);
               }
        }
$result = ”;
GetFileRootById(123, $result);
var_dump($result); // folder1/folder2/etc

P.S. Надо в редакторе заметок сделать нормальные вставки code, а то так он плохо читается.



15 Комментариев

  1. Phoebus | Февраль 22, 2008

    Потому что надо внимательно смотреть, что ты пишешь.
    Первый вариант не работает, потому что когда функция входит в рекурскию для поиска вышестоящего узла (dir_related_dir > 0; кстати, почему такое гнусное неочевидное название для очевидной вещи?), то результат не возвращается:
    else
    {
    $this->GetFileRootById($res[’dir_related_dir’], $root);
    }

  2. Gep | Февраль 22, 2008

    не понял, что значит “гнусное неочевидное название”?)
    кстати, если сделать
    if ($res[’dir_related_dir’] == 0){
    echo $root;
    return $root;
    }
    то выведет путь.
    Суть в том, что не срабатывает return $root;

  3. Phoebus | Февраль 22, 2008

    По пунктам
    1. Гнусное неочевидное — потому что dir_related_dir ни о чём не говорит. Parent_id скажет о поле в тысячу раз больше информации.
    2. Естественно выведет. Но это же рекурсивная функция которая _возвращает_ результат (это очень важный момент!), а значит что если ты вызываешь её внутри себя, то ты должен результат внутреннего вызова либо присваивать, либо делать return. Я тебе написал, где у тебя ошибка (нет return при вызове):
    else{
    $this->GetFileRootById($res[’dir_related_dir’], $root);
    }
    Если добавить тут return, то когда вызовы доберутся до корня дерева, то произойдёт возврат склеенного пути из самого “низа” рекурсивного стека наверх.

  4. Phoebus | Февраль 22, 2008

    Мне просто кажется, что ты тут не совсем понимаешь суть рекурсии. Если у тебя два узла (представь), то на первом узле ты вызовешь себя, где будет возвращен склеенный путь. Но ты ведь внутренний вызов никак не обрабатываешь, не присваиваешь и не возвращаешь.

  5. Gep | Февраль 22, 2008

    Хм.. может ты неправильно понял.. дело в том, что этот return вызывается в последний раз, и только один раз. echo в предыдущем комменте вызывается 1 раз.
    В конце рекурсии он сюда
    else{
    $this->GetFileRootById($res[’dir_related_dir’], $root);
    }
    не зайдет.

  6. Phoebus | Февраль 22, 2008

    Такого не бывает, чувак.
    Ты где-то что-то упускаешь.

    И я тебе скажу, что.
    У тебя ф-ция два раза вызывается. Первый раз вызываешь её ты, где происходит повторный рекурсивный вход, где и выводится $root. Но т.к. вызов рекурсивный, то результат не возвращается.

    Ещё раз говорю, добавь return. Это баг который сидит с транспаратном и орёт что он баг.

  7. Gep | Февраль 23, 2008

    >>>Ещё раз говорю, добавь return. Это баг который сидит с транспаратном и орёт что он баг

    Куда добавить return?

  8. Phoebus | Февраль 23, 2008

    Ты издеваешься?
    В результат рекурсивного вызова, я же два раза написал.
    У тебя:
    else{
    $this->GetFileRootById($res[’dir_related_dir’], $root);
    }

    А надо:
    else{
    return $this->GetFileRootById($res[’dir_related_dir’], $root);
    }

  9. Gep | Февраль 23, 2008

    Phoebus, ты абсолютно прав. Так и есть. :) Спасибо.

    >>>а значит что если ты вызываешь её внутри себя, то ты должен результат внутреннего вызова либо присваивать, либо делать return

    Этого я совсем не знал. Нелогично как-то:)

  10. Phoebus | Февраль 23, 2008

    Это же очевидно!
    Нарисуй на листе бумаги обычным карандашом стек, или представь это абстракцией, тут не надо знать “что результат рекурсии надо возвращать”, тут достаточно понимать, _что_ именно происходит когда ты рекурсивно вызываешь.

  11. Phoebus | Февраль 23, 2008

    >>>а значит что если ты вызываешь её внутри себя, то ты должен результат внутреннего вызова либо присваивать, либо делать return

    >Этого я совсем не знал. Нелогично как-то:)

    Забыл добавить “в данном случае”. Во втором случае ты хранишь всё в одной-единственной переменной, которую тягаешь по ссылки во все вызовы. Там соответсвенно никакой результат возвращать не нужно, потому что результат хранится и изменяется в одной для всех вызовов переменной.

  12. Няксъ | Апрель 3, 2008

    геп, ты спал перед тем как анализировать код и писать комменты? =)

  13. Gep | Апрель 3, 2008

    хватит тыкать меня в то, что спал я или нет) Тем более разговор был в феврале, я уже не вспомню. :) Может невнимательность по другим причинам.

  14. Няксъ | Апрель 3, 2008

    ой, она у меня сегодня в рсске как непрочтенная появилась, виноват. я еще удивился, вроде вечером не было, а феба видел час назад, как вы успали столько комментов настрочить =)

  15. Gep | Апрель 13, 2008

    Вообще говоря, я дамп восстанавливал, а эта запись была добавлена, изменена дата. В комментах вручную были изменены id поста.

Комментарий: