如何从平面阵列构建具有无限深度的嵌套HTML列表?

时间:2012-02-10 07:27:12

标签: php recursion

我正在尝试从源数组生成一个多级HTML列表,格式如下:

/**
 * id = unique id
 * parent_id = "id" that this item is directly nested under
 * text = the output string
 */
$list = array(
    array(
        'id'        =>  1,
        'parent_id' =>  0,
        'text'      =>  'Level 1',
    ), array(
        'id'        =>  2,
        'parent_id' =>  0,
        'text'      =>  'Level 2',
    ), array(
        'id'        =>  3,
        'parent_id' =>  2,
        'text'      =>  'Level 2.1',
    ), array(
        'id'        =>  4,
        'parent_id' =>  2,
        'text'      =>  'Level 2.2',
    ), array(
        'id'        =>  5,
        'parent_id' =>  4,
        'text'      =>  'Level 2.2.1',
    ), array(
        'id'        =>  6,
        'parent_id' =>  0,
        'text'      =>  'Level 3',
    )
);

目标是具有无限深度的嵌套<ul>。上面数组的预期输出是:

  • 等级1
  • 等级2
    • 等级2.1
    • 等级2.2
      • 等级2.2.1
  • 3级

如果只有数组项有一个名为child的键或者包含实际子数组的键,那么通过这些可以很容易地递归并使用如下函数得到所需的输出:

function makeList($list)
{
    echo '<ul>';
    foreach ($list as $item)
    {
        echo '<li>'.$item['text'];
        if (isset($item['child']))
        {
            makeList($item['child']);
        }
        echo '</li>';
    }
    echo '</ul>';
}

不幸的是,对我来说并非如此 - 源数组的格式无法更改。因此,很久以前我写了这个非常讨厌的函数来实现它,它只能工作三个级别(代码是逐字粘贴原始注释)。我知道这是一个漫长无聊的读物,请耐心等待:

function makeArray($links)
{
    // Output
    $nav = array();

    foreach ($links as $k => $v)
    {
        // If no parent_id is present, we can assume it is a top-level link
        if (empty($v['parent_id']))
        {
            $id = isset($v['id']) ? $v['id'] : $k;

            $nav[$id] = $v;

            // Remove from original array
            unset($links[$k]);
        }
    }

    // Loop through the remaining links again,
    // we can assume they all have a parent_id
    foreach ($links as $k => $v)
    {
        // Link's parent_id is in the top level array, so this is a level-2 link
        // We already looped through every item so we know they are all accounted for
        if (isset($nav[$v['parent_id']]))
        {
            $id = isset($v['id']) ? $v['id'] : $k;

            // Add it to the top level links as a child
            $nav[$v['parent_id']]['child'][$id] = $v;

            // Set a marker so we know which ones to loop through to add the third level
            $nav2[$id] = $v;

            // Remove it from the array
            unset($links[$k]);
        }
    }

    // Last iteration for the third level
    // All other links have been removed from the original array at this point
    foreach ($links as $k => $v)
    {
        $id = isset($v['id']) ? $v['id'] : $k;

        // Link's parent_id is in the second level array, so this is a level-3 link
        // Orphans will be ignored
        if (isset($nav2[$v['parent_id']]))
        {
            // This part is crazy, just go with it
            $nav3 = $nav2[$v['parent_id']]['parent_id'];
            $nav[$nav3]['child'][$v['parent_id']]['child'][] = $v;
        }

    }

    return $nav;
}

这使得数组如下:

array(
    'text' => 'Level 1'
    'child' => array(
        array(
            'text' => 'Level 1.2'
            'child' => array(
                array(
                    'text' => 'Level 1.2.1'
                    'child' => array(
                        // etc.
                   ),
                array(
                    'text' => 'Level 1.2.2'
                    'child' => array(
                        // etc.
                   ),
                )
             )
        )
    )
);

用法:

$nav = makeArray($links);
makeList($nav);

我花了很多时间试图解决这个问题,我在这里给出的原始代码仍然是我能够制作的最佳解决方案。

如果没有那个糟糕的功能(限制在3的深度),并且具有无限多个级别,我怎样才能实现这一点?对此有更优雅的解决方案吗?

3 个答案:

答案 0 :(得分:7)

打印:

function printListRecursive(&$list,$parent=0){
    $foundSome = false;
    for( $i=0,$c=count($list);$i<$c;$i++ ){
        if( $list[$i]['parent_id']==$parent ){
            if( $foundSome==false ){
                echo '<ul>';
                $foundSome = true;
            }
            echo '<li>'.$list[$i]['text'].'</li>';
            printListRecursive($list,$list[$i]['id']);
        }
    }
    if( $foundSome ){
        echo '</ul>';
    }
}

printListRecursive($list);

创建多维数组:

function makeListRecursive(&$list,$parent=0){
    $result = array();
    for( $i=0,$c=count($list);$i<$c;$i++ ){
        if( $list[$i]['parent_id']==$parent ){
            $list[$i]['childs'] = makeListRecursive($list,$list[$i]['id']);
            $result[] = $list[$i];
        }
    }
    return $result;
}

$result = array();
$result = makeListRecursive($list);
echo '<pre>';
var_dump($result);
echo '</pre>';

答案 1 :(得分:4)

经过测试和工作:)

$list = array(...);
$nested = array();

foreach ($list as $item)
{
    if ($item['parent_id'] == 0)
    {
        // Woot, easy - top level
        $nested[$item['id']] = $item;
    }
    else
    {
        // Not top level, find it's place
        process($item, $nested);
    }
}

// Recursive function
function process($item, &$arr)
{
    if (is_array($arr))
    {
        foreach ($arr as $key => $parent_item)
        {
            // Match?
            if (isset($parent_item['id']) && $parent_item['id'] == $item['parent_id'])
            {
                $arr[$key]['children'][$item['id']] = $item;
            }
            else
            {
                // Keep looking, recursively
                process($item, $arr[$key]);
            }
        }
    }
}

答案 2 :(得分:1)

我最近写的一些方法,也许有些方法会有所帮助,对不起,我的时间很短,无法根据您的需要重新编写。

此代码实际上是Kohana框架模型的一部分,方法->as_array()用于平展Database_Result对象。

function array_tree($all_nodes){
    $tree = array();
    foreach($all_nodes as $node){
        $tree[$node->id]['fields'] = $node->as_array();
        $tree[$node->id]['children'] = array();

        if($node->parent_id){
             $tree[$node->parent_id]['children'][$node->id] =& $tree[$node->id];
        }
    }


    $return_tree = array();
    foreach($tree as $node){
        if($node['fields']['depth'] == 0){
            $return_tree[$node['fields']['id']] = $node;
        }
    }

    return $return_tree;
}

array_tree()用于从平面阵列中制作树。关键特征是=&部分;)

function html_tree($tree_array = null){
        if( ! $tree_array){
           $tree_array = $this -> array_tree();
        }

        $html_tree = '<ul>'."\n";
        foreach($tree_array as $node){
            $html_tree .= $this->html_tree_crawl($node);
        }
        $html_tree .= '</ul>'."\n";


        return $html_tree;
    }

function html_tree_crawl($node){
        $children = null;

        if(count($node['children']) > 0){
            $children = '<ul>'."\n";
            foreach($node['children'] as $chnode){
                $children .= $this->html_tree_crawl($chnode);
            }
            $children .= '</ul>'."\n";
        }

        return $this->html_tree_node($node, $children);
    }

html_tree_node()是一种在HTML中显示当前节点和子节点的简单方法。 示例如下:

<li id="node-<?= $node['id'] ?>">
    <a href="#"><?= $node['title'] ?></a>
    <?= (isset($children) && $children != null) ? $children : '' ?>
</li>