Php 从数据库结果生成多维数组的递归函数

Php 从数据库结果生成多维数组的递归函数,php,arrays,function,recursion,Php,Arrays,Function,Recursion,我想写一个函数,它接受一个页面/类别数组(来自一个平面数据库结果),并根据父ID生成一个嵌套页面/类别项数组。我希望递归地这样做,以便可以进行任何级别的嵌套 例如:我在一个查询中获取所有页面,这就是数据库表的外观 +-------+---------------+---------------------------+ | id | parent_id | title | +-------+---------------+----------

我想写一个函数,它接受一个页面/类别数组(来自一个平面数据库结果),并根据父ID生成一个嵌套页面/类别项数组。我希望递归地这样做,以便可以进行任何级别的嵌套

例如:我在一个查询中获取所有页面,这就是数据库表的外观

+-------+---------------+---------------------------+
|   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
        )
)
我已经看过并尝试了几乎所有我遇到的解决方案(这里有很多关于堆栈溢出的解决方案,但是没有运气得到足够通用的、既适用于页面又适用于类别的解决方案)

这是我得到的最接近的结果,但它不起作用,因为我将孩子分配给第一级家长

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);
}

一些非常简单、通用的树构建:

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);
算法非常简单:

  • 获取所有元素的数组和当前父元素的id(最初为
    0
    /nothing/
    null
    /which)
  • 循环遍历所有元素
  • 如果元素的
    父级\u id
    与您在1中获得的当前父级id匹配,则该元素是父级的子级。请将其放入当前子级列表中(此处:
    $branch
  • 使用您刚刚在3中标识的元素的id递归调用该函数,即查找该元素的所有子元素,并将它们添加为
    子元素
    元素
  • 返回找到的子项的列表

  • 换句话说,此函数的一次执行返回一个元素列表,这些元素是给定父id的子元素。使用
    buildTree($myArray,1)调用它
    ,它将返回父id为1的元素列表。最初调用此函数时,父id为0,因此返回没有父id的元素,这些元素是根节点。该函数递归调用自身以查找子元素的子元素。

    可以使用php将mysql结果放入数组中,然后使用它

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

    我知道这个问题很老了,但我遇到了一个非常类似的问题——除了大量的数据。经过一些努力,我成功地在resultset的一个过程中构建了一棵树——使用引用。这段代码并不漂亮,但它工作起来很快。它是非递归的——也就是说,resultset上只有一个过程然后在末尾添加一个
    array\u 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
                                    (
                                    )
    
                            )
    
                    )
    
            )
    
    )
    

    这正是我想要的。

    从这里的其他答案中汲取灵感,我提出了自己的版本,通过使用自定义函数列表在每个级别获得分组键,递归地(任意深度)对assoc数组的数组进行分组

    这里是的简化版本(具有更多用于调整旋钮的参数)。请注意,它使用一个简单的作为子例程,用于在各个级别执行分组

    /**
     * - Groups a (non-associative) array items recursively, essentially converting it into a nested
     *   tree or JSON like structure. Inspiration taken from: https://stackoverflow.com/a/8587437/3679900
     * OR
     * - Converts an (non-associative) array of items into a multi-dimensional array by using series
     *   of callables $key_retrievers and recursion
     *
     * - This function is an extension to above 'groupByFn', which also groups array but only till 1 (depth) level
     *   (whereas this one does it till any number of depth levels by using recursion)
     * - Check unit-tests to understand further
     * @param array $data Array[mixed] (non-associative) array of items that has to be grouped / converted to
     *                    multi-dimensional array
     * @param array $key_retrievers Array[Callable[[mixed], int|string]]
     *                    - A list of functions applied to item one-by-one, to determine which
     *                    (key) bucket an item goes into at different levels
     *                    OR
     *                    - A list of callables each of which takes an item or input array as input and returns an int
     *                    or string which is to be used as a (grouping) key for generating multi-dimensional array.
     * @return array A nested assoc-array / multi-dimensional array generated by 'grouping' items of
     *               input $data array at different levels by application of $key_retrievers on them (one-by-one)
     */
    public static function groupByFnRecursive(
        array $data,
        array $key_retrievers
    ): array {
        // in following expression we are checking for array-length = 0 (and not nullability)
        // why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
        if (empty($data)) {
            // edge-case: if the input $data array is empty, return it unmodified (no need to check for other args)
            return $data;
    
            // in following expression we are checking for array-length = 0 (and not nullability)
            // why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900
        } elseif (empty($key_retrievers)) {
            // base-case of recursion: when all 'grouping' / 'nesting' into multi-dimensional array has been done,
            return $data;
        } else {
            // group the array by 1st key_retriever
            $grouped_data = self::groupByFn($data, $key_retrievers[0]);
            // remove 1st key_retriever from list
            array_shift($key_retrievers);
    
            // and then recurse into further levels
            // note that here we are able to use array_map (and need not use array_walk) because array_map can preserve
            // keys as told here:
            // https://www.php.net/manual/en/function.array-map.php#refsect1-function.array-map-returnvalues
            return array_map(
                static function (array $item) use ($key_retrievers): array {
                    return self::groupByFnRecursive($item, $key_retrievers);
                },
                $grouped_data
            );
        }
    }
    

    请检查Biger with

    的要点,你不能只使用一个包含所有父ID的数组和另一个页面数组吗?很高兴这有帮助。注意:这有点低效,因为它总是向下传递整个
    $elements
    数组。对于不重要的小数组,但对于大数据集,你需要删除already在传递之前匹配了其中的元素。但这会变得有些混乱,所以为了便于您理解,我将其保留为简单的。:@deceze我也希望看到混乱的版本。提前谢谢!请解释一下buildTree()中的第一个参数“array”是什么?这是否应该是您给初始页面数组等的变量,例如“$tree=array”?还有人能解释最后一行“$tree=buildTree($rows)”吗,因为“$rows”在任何地方都没有定义?最后,我正在努力使用HTML标记生成嵌套列表。@user
    array
    $elements
    的类型提示,第一个参数只是
    array$elements
    $rows
    是一个数据库结果数组,类似于问题中的结果。@用户我已经添加了一个解释。忽略
    $children=buildTree(…)
    部分,函数应该非常明显和简单。虽然解决方案很聪明,但这段代码有bug,但它在不同的情况下给了我不同的结果situations@Mohammadhzp去年我一直在生产中使用此解决方案,没有任何问题。如果您的数据不同,您将得到不同的结果:)@AleksG:I在评论之前更新您的答案,我必须将isset($elem['children'])添加到数组过滤器回调中,类似于返回isset($elem['children'])和is_null($elem['n\u parent\u id');让它工作right@Mohammadhzp通过您的更改,如果顶级元素没有任何子元素,代码将不起作用-它将从数组中完全删除。@AleksG,使用最新版本的php,如果我删除isset($elem['children']),并且顶级元素包含子元素,则结果对我来说不是这样,我会得到顶级元素的两个不同数组(一个有子元素,一个没有子元素)“我2分钟前刚刚再次测试,没有isset(),我得到了不同(错误)的结果”