Рекурсивная функция для генерации многомерного массива из результата базы данных

Я хочу написать функцию, которая принимает массив страниц/категорий (из результата плоской базы данных) и генерирует массив вложенных элементов страницы/категории на основе идентификаторов родительских элементов. Я хотел бы сделать это рекурсивно, чтобы можно было выполнить любой уровень вложенности.

Например: я извлекаю все страницы в одном запросе, и это выглядит так, как выглядит таблица базы данных

+-------+---------------+---------------------------+
|   id  |   parent_id   |           title           |
+-------+---------------+---------------------------+
|   1   |       0       |   Parent Page             |
|   2   |       1       |   Sub Page                |
|   3   |       2       |   Sub Sub Page            |
|   4   |       0       |   Another Parent Page     |
+-------+---------------+---------------------------+

И это массив, который я хотел бы обработать в моих файлах просмотра:

Array
(
    [0] => Array
        (
            [id] => 1
            [parent_id] => 0
            [title] => Parent Page
            [children] => Array
                        (
                            [0] => Array
                                (
                                    [id] => 2
                                    [parent_id] => 1
                                    [title] => Sub Page
                                    [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [id] => 3
                                                            [parent_id] => 1
                                                            [title] => Sub Sub Page
                                                        )
                                                )
                                )
                        )
        )
    [1] => Array
        (
            [id] => 4
            [parent_id] => 0
            [title] => Another Parent Page
        )
)

Я посмотрел и попробовал почти все решения, с которыми я столкнулся (там много их здесь, в Stack Overflow, но не повезло получить что-то общее, которое будет работать как для страниц, так и для категорий.

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

function page_walk($array, $parent_id = FALSE)
{   
    $organized_pages = array();

    $children = array();

    foreach($array as $index => $page)
    {
        if ( $page['parent_id'] == 0) // No, just spit it out and you're done
        {
            $organized_pages[$index] = $page;
        }
        else // If it does, 
        {       
            $organized_pages[$parent_id]['children'][$page['id']] = $this->page_walk($page, $parent_id);
        }
    }

    return $organized_pages;
}

function page_list($array)
{       
    $fakepages = array();
    $fakepages[0] = array('id' => 1, 'parent_id' => 0, 'title' => 'Parent Page');
    $fakepages[1] = array('id' => 2, 'parent_id' => 1, 'title' => 'Sub Page');
    $fakepages[2] = array('id' => 3, 'parent_id' => 2, 'title' => 'Sub Sub Page');
    $fakepages[3] = array('id' => 4, 'parent_id' => 3, 'title' => 'Another Parent Page');

    $pages = $this->page_walk($fakepages, 0);

    print_r($pages);
}

Ответ 1

Несколько очень простых, общих деревьев:

function buildTree(array $elements, $parentId = 0) {
    $branch = array();

    foreach ($elements as $element) {
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }

    return $branch;
}

$tree = buildTree($rows);

Алгоритм довольно прост:

  • Возьмите массив всех элементов и идентификатор текущего родителя (изначально 0/nothing/null/whatever).
  • Прокрутите все элементы.
  • Если элемент parent_id элемента совпадает с текущим родительским идентификатором, который вы получили в 1., этот элемент является дочерним по отношению к родительскому элементу. Поместите его в список текущих детей (здесь: $branch).
  • Вызывать функцию рекурсивно с идентификатором элемента, который вы только что идентифицировали в 3., т.е. найти все дочерние элементы этого элемента и добавить их как элемент children.
  • Верните список найденных детей.

Другими словами, одно выполнение этой функции возвращает список элементов, которые являются дочерними элементами данного родительского идентификатора. Назовите его buildTree($myArray, 1), он вернет список элементов, у которых есть родительский идентификатор 1. Первоначально эта функция вызывается с родительским идентификатором, равным 0, поэтому возвращаются элементы без родительского идентификатора, которые являются корневыми узлами. Функция вызывает себя рекурсивно, чтобы найти детей детей.

Ответ 2

Я знаю, что этот вопрос старый, но я столкнулся с очень похожими проблемами - за исключением очень большого количества данных. После некоторой борьбы мне удалось построить дерево за один проход из набора результатов - используя ссылки. Этот код не очень хорош, но он работает, и он работает довольно быстро. Он не рекурсивный - то есть только один проход по набору результатов, а затем один array_filter в конце:

$dbh = new PDO(CONNECT_STRING, USERNAME, PASSWORD);
$dbs = $dbh->query("SELECT n_id, n_parent_id from test_table order by n_parent_id, n_id");
$elems = array();

while(($row = $dbs->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
    $row['children'] = array();
    $vn = "row" . $row['n_id'];
    ${$vn} = $row;
    if(!is_null($row['n_parent_id'])) {
        $vp = "parent" . $row['n_parent_id'];
        if(isset($data[$row['n_parent_id']])) {
            ${$vp} = $data[$row['n_parent_id']];
        }
        else {
            ${$vp} = array('n_id' => $row['n_parent_id'], 'n_parent_id' => null, 'children' => array());
            $data[$row['n_parent_id']] = &${$vp};
        }
        ${$vp}['children'][] = &${$vn};
        $data[$row['n_parent_id']] = ${$vp};
    }
    $data[$row['n_id']] = &${$vn};
}
$dbs->closeCursor();

$result = array_filter($data, function($elem) { return is_null($elem['n_parent_id']); });
print_r($result);

При выполнении по этим данным:

mysql> select * from test_table;
+------+-------------+
| n_id | n_parent_id |
+------+-------------+
|    1 |        NULL |
|    2 |        NULL |
|    3 |           1 |
|    4 |           1 |
|    5 |           2 |
|    6 |           2 |
|    7 |           5 |
|    8 |           5 |
+------+-------------+

Последний print_r производит этот вывод:

Array
(
    [1] => Array
        (
            [n_id] => 1
            [n_parent_id] => 
            [children] => Array
                (
                    [3] => Array
                        (
                            [n_id] => 3
                            [n_parent_id] => 1
                            [children] => Array
                                (
                                )

                        )

                    [4] => Array
                        (
                            [n_id] => 4
                            [n_parent_id] => 1
                            [children] => Array
                                (
                                )

                        )

                )

        )

    [2] => Array
        (
            [n_id] => 2
            [n_parent_id] => 
            [children] => Array
                (
                    [5] => Array
                        (
                            [n_id] => 5
                            [n_parent_id] => 2
                            [children] => Array
                                (
                                    [7] => Array
                                        (
                                            [n_id] => 7
                                            [n_parent_id] => 5
                                            [children] => Array
                                                (
                                                )

                                        )

                                    [8] => Array
                                        (
                                            [n_id] => 8
                                            [n_parent_id] => 5
                                            [children] => Array
                                                (
                                                )

                                        )

                                )

                        )

                    [6] => Array
                        (
                            [n_id] => 6
                            [n_parent_id] => 2
                            [children] => Array
                                (
                                )

                        )

                )

        )

)

Это именно то, что я искал.

Ответ 3

Можно использовать php, чтобы получить результат mysql в массив, а затем использовать его.

$categoryArr = Array();
while($categoryRow = mysql_fetch_array($category_query_result)){
    $categoryArr[] = array('parentid'=>$categoryRow['parent_id'],
            'id'=>$categoryRow['id']);
   }

Ответ 4

Для больших массивов:

передать массив по ссылке.

function buildTree(&$elements, $parentId = 0) {   //pass main array by reference
    $branch = array();

    foreach ($elements as $key => $element) {
        if ($element['parent_id'] == $parentId) {

            $element['children'] = buildTree($elements, $element['id']);

            $branch[] = $element;                 
        }
    }
    return $branch;
}

$tree = buildTree($rows);

Если вы передаете массив be reference, то тот же массив будет использовать в каждой рекурсивной функции, нет необходимости объединять родительские дочерние массивы в конце

Ответ 5

Почему эта функция buildTree не работает на моем массиве.

Array ([5] => Array ([id] => 5 [parent_id] => 4 [data_level] => [data_prefix] => [data_title] => Активы [data_link] => 0)

[57] => Массив ([id] => 57 [parent_id] => 5 [data_level] => [data_prefix] => [data_title] => Основные средства [data_link] => 0)

[52] => Массив ([id] => 52 [parent_id] => 5 [data_level] => [data_prefix] => [data_title] => Второй актив [data_link] => 1)

[51] => Массив ([id] => 51 [parent_id] => 5 [data_level] => [data_prefix] => [data_title] => Assset ONE [data_link] => 1)

[48] => Массив ([id] => 48 [parent_id] => 4 [data_level] => [data_prefix] => [data_title] => Расходы [data_link] => 0)

[50] => Массив ([id] => 50 [parent_id] => 48 [уровень_данных] => [data_prefix] => [data_title] => Первый расход [data_link] => 1)

[49] => Массив ([id] => 49 [parent_id] => 48 [data_level] => [data_prefix] => [data_title] => Первый расход [data_link] => 1)

[58] => Массив ([id] => 58 [parent_id] => 57 [data_level] => [data_prefix] => [data_title] => Транспортные средства [data_link] => 1)

)