【问题标题】:Convert a series of parent-child relationships into a hierarchical tree?将一系列父子关系转化为层次树?
【发布时间】:2011-02-24 08:05:23
【问题描述】:

我有一堆名称-父名称对,我希望将它们变成尽可能少的层次树结构。例如,这些可能是配对:

Child : Parent
    H : G
    F : G
    G : D
    E : D
    A : E
    B : C
    C : E
    D : NULL

需要转化为(a)层次树:

D
├── E
│   ├── A
│   │   └── B
│   └── C   
└── G
    ├── F
    └── H

我想要的最终结果是一组嵌套的 <ul> 元素,每个 <li> 都包含孩子的名字。

配对中没有不一致(孩子是它自己的父母,父母是孩子的孩子等),因此可能可以进行一系列优化。

在 PHP 中,我如何从包含子 => 父对的数组转到一组嵌套的 <ul>s?

我感觉涉及到递归,但我还没有清醒到想通。

【问题讨论】:

    标签: php recursion tree hierarchical-data


    【解决方案1】:

    这需要一个非常基本的递归函数将子/父对解析为树结构,并需要另一个递归函数将其打印出来。只有一个功能就足够了,但为了清楚起见,这里有两个(可以在此答案的末尾找到一个组合功能)。

    首先初始化子/父对数组:

    $tree = array(
        'H' => 'G',
        'F' => 'G',
        'G' => 'D',
        'E' => 'D',
        'A' => 'E',
        'B' => 'C',
        'C' => 'E',
        'D' => null
    );
    

    然后是将该数组解析为层次树结构的函数:

    function parseTree($tree, $root = null) {
        $return = array();
        # Traverse the tree and search for direct children of the root
        foreach($tree as $child => $parent) {
            # A direct child is found
            if($parent == $root) {
                # Remove item from tree (we don't need to traverse this again)
                unset($tree[$child]);
                # Append the child into result array and parse its children
                $return[] = array(
                    'name' => $child,
                    'children' => parseTree($tree, $child)
                );
            }
        }
        return empty($return) ? null : $return;    
    }
    

    还有一个函数遍历该树以打印出无序列表:

    function printTree($tree) {
        if(!is_null($tree) && count($tree) > 0) {
            echo '<ul>';
            foreach($tree as $node) {
                echo '<li>'.$node['name'];
                printTree($node['children']);
                echo '</li>';
            }
            echo '</ul>';
        }
    }
    

    以及实际用法:

    $result = parseTree($tree);
    printTree($result);
    

    这是$result的内容:

    Array(
        [0] => Array(
            [name] => D
            [children] => Array(
                [0] => Array(
                    [name] => G
                    [children] => Array(
                        [0] => Array(
                            [name] => H
                            [children] => NULL
                        )
                        [1] => Array(
                            [name] => F
                            [children] => NULL
                        )
                    )
                )
                [1] => Array(
                    [name] => E
                    [children] => Array(
                        [0] => Array(
                            [name] => A
                            [children] => NULL
                        )
                        [1] => Array(
                            [name] => C
                            [children] => Array(
                                [0] => Array(
                                    [name] => B
                                    [children] => NULL
                                )
                            )
                        )
                    )
                )
            )
        )
    )
    

    如果您想要更高的效率,您可以将这些功能合二为一并减少迭代次数:

    function parseAndPrintTree($root, $tree) {
        $return = array();
        if(!is_null($tree) && count($tree) > 0) {
            echo '<ul>';
            foreach($tree as $child => $parent) {
                if($parent == $root) {                    
                    unset($tree[$child]);
                    echo '<li>'.$child;
                    parseAndPrintTree($child, $tree);
                    echo '</li>';
                }
            }
            echo '</ul>';
        }
    }
    

    在这样小的数据集上,您只能节省 8 次迭代,但在更大的数据集上,它可能会有所作为。

    【讨论】:

    • 大图。如何将 printTree 函数更改为不直接回显树的 html,而是将所有输出 html 保存到变量中并返回它?谢谢
    • 嗨,我认为函数声明必须是 parseAndPrintTree($tree, $root = null) 并且递归调用应该是 parseAndPrintTree($child, $tree);最好的问候
    【解决方案2】:

    Yet another Function To Make A Tree(不涉及递归,使用引用代替):

    $array = array('H' => 'G', 'F' => 'G', ..., 'D' => null);
    
    function to_tree($array)
    {
        $flat = array();
        $tree = array();
    
        foreach ($array as $child => $parent) {
            if (!isset($flat[$child])) {
                $flat[$child] = array();
            }
            if (!empty($parent)) {
                $flat[$parent][$child] =& $flat[$child];
            } else {
                $tree[$child] =& $flat[$child];
            }
        }
    
        return $tree;
    }
    

    返回一个像这样的分层数组:

    Array(
        [D] => Array(
            [G] => Array(
                [H] => Array()
                [F] => Array()
            )
            ...
        )
    )
    

    使用递归函数可以轻松地将其打印为 HTML 列表。

    【讨论】:

    • +1 - 非常聪明。虽然我发现递归解决方案更合乎逻辑。但我更喜欢你的函数的输出格式。
    • @Eric 更合乎逻辑?我不敢苟同。递归中没有任何“逻辑”。 OTOH 在解析递归函数/调用时会产生严重的认知开销。如果没有明确的堆栈分配,我会每天进行迭代而不是递归。
    【解决方案3】:

    $tree 中的平面结构转换为层次结构的另一种更简化的方法。公开它只需要一个临时数组:

    // add children to parents
    $flat = array(); # temporary array
    foreach ($tree as $name => $parent)
    {
        $flat[$name]['name'] = $name; # self
        if (NULL === $parent)
        {
            # no parent, is root element, assign it to $tree
            $tree = &$flat[$name]; 
        }
        else
        {
            # has parent, add self as child    
            $flat[$parent]['children'][] = &$flat[$name];
        }
    }
    unset($flat);
    

    这就是将层次结构放入多维数组的全部内容:

    Array
    (
        [children] => Array
            (
                [0] => Array
                    (
                        [children] => Array
                            (
                                [0] => Array
                                    (
                                        [name] => H
                                    )
    
                                [1] => Array
                                    (
                                        [name] => F
                                    )
    
                            )
    
                        [name] => G
                    )
    
                [1] => Array
                    (
                        [name] => E
                        [children] => Array
                            (
                                [0] => Array
                                    (
                                        [name] => A
                                    )
    
                                [1] => Array
                                    (
                                        [children] => Array
                                            (
                                                [0] => Array
                                                    (
                                                        [name] => B
                                                    )
    
                                            )
    
                                        [name] => C
                                    )
    
                            )
    
                    )
    
            )
    
        [name] => D
    )
    

    如果您想避免递归(可能是大型结构的负担),输出就不那么简单了。

    我一直想解决输出数组的 UL/LI “困境”。困境是,每个项目不知道孩子是否会跟进或需要关闭多少前面的元素。在另一个答案中,我已经通过使用RecursiveIteratorIterator 并寻找getDepth() 和我自己编写的Iterator 提供的其他元信息解决了这个问题:Getting nested set model into a &lt;ul&gt; but hiding “closed” subtreesanswer 也表明,使用迭代器时您非常灵活。

    但是,这是一个预先排序的列表,因此不适合您的示例。此外,我一直想用一种标准的树结构和 HTML 的 &lt;ul&gt;&lt;li&gt; 元素来解决这个问题。

    我提出的基本概念如下:

    1. TreeNode - 将每个元素抽象为一个简单的TreeNode 类型,该类型可以提供它的值(例如Name)以及它是否有子元素。
    2. TreeNodesIterator - 一个RecursiveIterator,能够遍历这些TreeNodes 的集合(数组)。这相当简单,因为 TreeNode 类型已经知道它是否有孩子以及哪些孩子。
    3. RecursiveListIterator - 一个RecursiveIteratorIterator,它具有递归迭代任何类型的RecursiveIterator 时所需的所有事件:
      • beginIteration / endIteration - 主列表的开始和结束。
      • beginElement / endElement - 每个元素的开始和结束。
      • beginChildren / endChildren - 每个子列表的开始和结束。 这个RecursiveListIterator 仅以函数调用的形式提供这些事件。子列表,因为它是典型的 &lt;ul&gt;&lt;li&gt; 列表,在其父 &lt;li&gt; 元素内打开和关闭。因此,endElement 事件在相应的endChildren 事件之后触发。这可以更改或可配置以扩大此类的使用。这些事件作为函数调用分发给装饰器对象,然后将它们分开。
    4. ListDecorator - 一个“装饰器”类,它只是RecursiveListIterator 事件的接收者。

    我从主输出逻辑开始。采用现在分层的$tree 数组,最终代码如下所示:

    $root = new TreeNode($tree);
    $it = new TreeNodesIterator(array($root));
    $rit = new RecursiveListIterator($it);
    $decor = new ListDecorator($rit);
    $rit->addDecorator($decor);
    
    foreach($rit as $item)
    {
        $inset = $decor->inset(1);
        printf("%s%s\n", $inset, $item->getName());
    }
    

    首先让我们看一下ListDecorator,它简单地包装了&lt;ul&gt;&lt;li&gt; 元素并决定如何输出列表结构:

    class ListDecorator
    {
        private $iterator;
        public function __construct(RecursiveListIterator $iterator)
        {
            $this->iterator = $iterator;
        }
        public function inset($add = 0)
        {
            return str_repeat('  ', $this->iterator->getDepth()*2+$add);
        }
    

    构造函数采用它正在处理的列表迭代器。 inset 只是一个帮助函数,用于很好地缩进输出。其余的只是每个事件的输出函数:

        public function beginElement()
        {
            printf("%s<li>\n", $this->inset());
        }
        public function endElement()
        {
            printf("%s</li>\n", $this->inset());
        }
        public function beginChildren()
        {
            printf("%s<ul>\n", $this->inset(-1));
        }
        public function endChildren()
        {
            printf("%s</ul>\n", $this->inset(-1));
        }
        public function beginIteration()
        {
            printf("%s<ul>\n", $this->inset());
        }
        public function endIteration()
        {
            printf("%s</ul>\n", $this->inset());
        }
    }
    

    考虑到这些输出功能,这又是主要的输出总结/循环,我一步一步来:

    $root = new TreeNode($tree);
    

    创建将用于开始迭代的根TreeNode

    $it = new TreeNodesIterator(array($root));
    

    这个TreeNodesIterator 是一个RecursiveIterator,它可以在单个$root 节点上进行递归迭代。它作为数组传递,因为该类需要迭代一些东西并允许与一组子元素重用,这些子元素也是TreeNode 元素的数组。

    $rit = new RecursiveListIterator($it);
    

    这个RecursiveListIterator 是一个提供上述事件的RecursiveIteratorIterator。要使用它,只需要提供一个ListDecorator(上面的类)并分配addDecorator

    $decor = new ListDecorator($rit);
    $rit->addDecorator($decor);
    

    然后一切都设置为仅foreach 并输出每个节点:

    foreach($rit as $item)
    {
        $inset = $decor->inset(1);
        printf("%s%s\n", $inset, $item->getName());
    }
    

    如本例所示,整个输出逻辑被封装在ListDecorator 类和单个foreach 中。整个递归遍历已经完全封装在 SPL 递归迭代器中,它提供了一个堆栈过程,这意味着内部没有进行递归函数调用。

    基于事件的ListDecorator 允许您专门修改输出并为同一数据结构提供多种类型的列表。甚至可以更改输入,因为数组数据已封装到 TreeNode

    完整代码示例:

    <?php
    namespace My;
    
    $tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);
    
    // add children to parents
    $flat = array(); # temporary array
    foreach ($tree as $name => $parent)
    {
        $flat[$name]['name'] = $name; # self
        if (NULL === $parent)
        {
            # no parent, is root element, assign it to $tree
            $tree = &$flat[$name];
        }
        else
        {
            # has parent, add self as child    
            $flat[$parent]['children'][] = &$flat[$name];
        }
    }
    unset($flat);
    
    class TreeNode
    {
        protected $data;
        public function __construct(array $element)
        {
            if (!isset($element['name']))
                throw new InvalidArgumentException('Element has no name.');
    
            if (isset($element['children']) && !is_array($element['children']))
                throw new InvalidArgumentException('Element has invalid children.');
    
            $this->data = $element;
        }
        public function getName()
        {
             return $this->data['name'];
        }
        public function hasChildren()
        {
            return isset($this->data['children']) && count($this->data['children']);
        }
        /**
         * @return array of child TreeNode elements 
         */
        public function getChildren()
        {        
            $children = $this->hasChildren() ? $this->data['children'] : array();
            $class = get_called_class();
            foreach($children as &$element)
            {
                $element = new $class($element);
            }
            unset($element);        
            return $children;
        }
    }
    
    class TreeNodesIterator implements \RecursiveIterator
    {
        private $nodes;
        public function __construct(array $nodes)
        {
            $this->nodes = new \ArrayIterator($nodes);
        }
        public function  getInnerIterator()
        {
            return $this->nodes;
        }
        public function getChildren()
        {
            return new TreeNodesIterator($this->nodes->current()->getChildren());
        }
        public function hasChildren()
        {
            return $this->nodes->current()->hasChildren();
        }
        public function rewind()
        {
            $this->nodes->rewind();
        }
        public function valid()
        {
            return $this->nodes->valid();
        }   
        public function current()
        {
            return $this->nodes->current();
        }
        public function key()
        {
            return $this->nodes->key();
        }
        public function next()
        {
            return $this->nodes->next();
        }
    }
    
    class RecursiveListIterator extends \RecursiveIteratorIterator
    {
        private $elements;
        /**
         * @var ListDecorator
         */
        private $decorator;
        public function addDecorator(ListDecorator $decorator)
        {
            $this->decorator = $decorator;
        }
        public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
        {
            parent::__construct($iterator, $mode, $flags);
        }
        private function event($name)
        {
            // event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]);
            $callback = array($this->decorator, $name);
            is_callable($callback) && call_user_func($callback);
        }
        public function beginElement()
        {
            $this->event('beginElement');
        }
        public function beginChildren()
        {
            $this->event('beginChildren');
        }
        public function endChildren()
        {
            $this->testEndElement();
            $this->event('endChildren');
        }
        private function testEndElement($depthOffset = 0)
        {
            $depth = $this->getDepth() + $depthOffset;      
            isset($this->elements[$depth]) || $this->elements[$depth] = 0;
            $this->elements[$depth] && $this->event('endElement');
    
        }
        public function nextElement()
        {
            $this->testEndElement();
            $this->event('{nextElement}');
            $this->event('beginElement');       
            $this->elements[$this->getDepth()] = 1;
        } 
        public function beginIteration()
        {
            $this->event('beginIteration');
        }
        public function endIteration()
        {
            $this->testEndElement();
            $this->event('endIteration');       
        }
    }
    
    class ListDecorator
    {
        private $iterator;
        public function __construct(RecursiveListIterator $iterator)
        {
            $this->iterator = $iterator;
        }
        public function inset($add = 0)
        {
            return str_repeat('  ', $this->iterator->getDepth()*2+$add);
        }
        public function beginElement()
        {
            printf("%s<li>\n", $this->inset(1));
        }
        public function endElement()
        {
            printf("%s</li>\n", $this->inset(1));
        }
        public function beginChildren()
        {
            printf("%s<ul>\n", $this->inset());
        }
        public function endChildren()
        {
            printf("%s</ul>\n", $this->inset());
        }
        public function beginIteration()
        {
            printf("%s<ul>\n", $this->inset());
        }
        public function endIteration()
        {
            printf("%s</ul>\n", $this->inset());
        }
    }
    
    
    $root = new TreeNode($tree);
    $it = new TreeNodesIterator(array($root));
    $rit = new RecursiveListIterator($it);
    $decor = new ListDecorator($rit);
    $rit->addDecorator($decor);
    
    foreach($rit as $item)
    {
        $inset = $decor->inset(2);
        printf("%s%s\n", $inset, $item->getName());
    }
    

    输出:

    <ul>
      <li>
        D
        <ul>
          <li>
            G
            <ul>
              <li>
                H
              </li>
              <li>
                F
              </li>
            </ul>
          </li>
          <li>
            E
            <ul>
              </li>
              <li>
                A
              </li>
              <li>
                C
                <ul>
                  <li>
                    B
                  </li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
    

    Demo (PHP 5.2 variant)

    一个可能的变体是迭代任何RecursiveIterator 并提供对所有可能发生的事件的迭代的迭代器。然后 foreach 循环中的 switch / case 可以处理这些事件。

    相关:

    【讨论】:

    • 像这个解决方案一样“精心设计”——它究竟是如何比前面的例子“更简化”的——它似乎是针对同一问题的过度设计解决方案
    • @Andre:按封装 IIRC 的等级。在另一个相关的答案中,我有一个完全非封装的代码片段,它要小得多,因此可能会“更简化”,具体取决于 POV。
    • @hakre 如何修改“ListDecorator”类以将“id”添加到从树数组中获取的 LI?
    • @Gangesh:最容易使用节点访问者。 ^^ 开个玩笑,直接就是扩展装饰器并编辑beginElement(),得到内部迭代器(示例见inset()方法)和带有id属性的工作。
    • @hakre 谢谢。我会试试的。
    【解决方案4】:

    虽然Alexander-Konstantinov 的解决方案一开始可能看起来不那么容易阅读,但它既天才又在性能方面呈指数级提升,这应该被评为最佳答案。

    感谢朋友,我做了一个基准来比较这篇文章中提出的两种解决方案。

    我有一个 @250k 平面树,需要转换 6 个级别,我正在寻找一种更好的方法来避免递归迭代。

    递归与引用:

    // Generate a 6 level flat tree
    $root = null;
    $lvl1 = 13;
    $lvl2 = 11;
    $lvl3 = 7;
    $lvl4 = 5;
    $lvl5 = 3;
    $lvl6 = 1;    
    $flatTree = [];
    for ($i = 1; $i <= 450000; $i++) {
        if ($i % 3 == 0)  { $lvl5 = $i; $flatTree[$lvl6] = $lvl5; continue; }
        if ($i % 5 == 0)  { $lvl4 = $i; $flatTree[$lvl5] = $lvl4; continue; }
        if ($i % 7 == 0)  { $lvl3 = $i; $flatTree[$lvl3] = $lvl2; continue; }
        if ($i % 11 == 0) { $lvl2 = $i; $flatTree[$lvl2] = $lvl1; continue; }
        if ($i % 13 == 0) { $lvl1 = $i; $flatTree[$lvl1] = $root; continue; }
        $lvl6 = $i;
    }
    
    echo 'Array count: ', count($flatTree), PHP_EOL;
    
    // Reference function
    function treeByReference($flatTree)
    {
        $flat = [];
        $tree = [];
    
        foreach ($flatTree as $child => $parent) {
            if (!isset($flat[$child])) {
                $flat[$child] = [];
            }
            if (!empty($parent)) {
                $flat[$parent][$child] =& $flat[$child];
            } else {
                $tree[$child] =& $flat[$child];
            }
        }
    
        return $tree;
    }
    
    // Recursion function
    function treeByRecursion($flatTree, $root = null)
    {
        $return = [];
        foreach($flatTree as $child => $parent) {
            if ($parent == $root) {
                unset($flatTree[$child]);
                $return[$child] = treeByRecursion($flatTree, $child);
            }
        }
        return $return ?: [];
    }
    
    // Benchmark reference
    $t1 = microtime(true);
    $tree = treeByReference($flatTree);
    echo 'Reference: ', (microtime(true) - $t1), PHP_EOL;
    
    // Benchmark recursion
    $t2 = microtime(true);
    $tree = treeByRecursion($flatTree);
    echo 'Recursion: ', (microtime(true) - $t2), PHP_EOL;
    

    输出不言自明:

    Array count: 255493
    Reference: 0.3259289264679 (less than 0.4s)
    Recursion: 6604.9865279198 (almost 2h)
    

    【讨论】:

      【解决方案5】:

      嗯,首先我会将键值对的直接数组转换为分层数组

      function convertToHeiarchical(array $input) {
          $parents = array();
          $root = array();
          $children = array();
          foreach ($input as $item) {
              $parents[$item['id']] = &$item;
              if ($item['parent_id']) {
                  if (!isset($children[$item['parent_id']])) {
                      $children[$item['parent_id']] = array();
                  }
                  $children[$item['parent_id']][] = &$item;
              } else {
                  $root = $item['id'];
              }
          }
          foreach ($parents as $id => &$item) {
              if (isset($children[$id])) {
                  $item['children'] = $children[$id];
              } else {
                  $item['children'] = array();
              }
          }
          return $parents[$root];
      }
      

      这可以将具有 parent_id 和 id 的平面数组转换为分层数组:

      $item = array(
          'id' => 'A',
          'blah' => 'blah',
          'children' => array(
              array(
                  'id' => 'B',
                  'blah' => 'blah',
                  'children' => array(
                      array(
                          'id' => 'C',
                          'blah' => 'blah',
                          'children' => array(),
                      ),
                   ),
                  'id' => 'D',
                  'blah' => 'blah',
                  'children' => array(
                      array(
                          'id' => 'E',
                          'blah' => 'blah',
                          'children' => array(),
                      ),
                  ),
              ),
          ),
      );
      

      然后,只需创建一个渲染函数:

      function renderItem($item) {
          $out = "Your OUtput For Each Item Here";
          $out .= "<ul>";
          foreach ($item['children'] as $child) {
              $out .= "<li>".renderItem($child)."</li>";
          }
          $out .= "</ul>";
          return $out;
      }
      

      【讨论】:

        【解决方案6】:

        好吧,要解析成 UL 和 LI,应该是这样的:

        $array = array (
            'H' => 'G'
            'F' => 'G'
            'G' => 'D'
            'E' => 'D'
            'A' => 'E'
            'B' => 'C'
            'C' => 'E'
            'D' => 'NULL'
        );
        
        
        recurse_uls ($array, 'NULL');
        
        function recurse_uls ($array, $parent)
        {
            echo '<ul>';
            foreach ($array as $c => $p)  {
                if ($p != $parent) continue;
                echo '<li>'.$c.'</li>';
                recurse_uls ($array, $c);
            }
            echo '</ul>';
        }
        

        但我很想看到一个不需要您如此频繁地遍历数组的解决方案...

        【讨论】:

          【解决方案7】:

          这是我想出的:

          $arr = array(
                      'H' => 'G',
                      'F' => 'G',
                      'G' => 'D',
                      'E' => 'D',
                      'A' => 'E',
                      'B' => 'C',
                      'C' => 'E',
                      'D' => null );
          
              $nested = parentChild($arr);
              print_r($nested);
          
              function parentChild(&$arr, $parent = false) {
                if( !$parent) { //initial call
                   $rootKey = array_search( null, $arr);
                   return array($rootKey => parentChild($arr, $rootKey));
                }else { // recursing through
                  $keys = array_keys($arr, $parent);
                  $piece = array();
                  if($keys) { // found children, so handle them
                    if( !is_array($keys) ) { // only one child
                      $piece = parentChild($arr, $keys);
                     }else{ // multiple children
                       foreach( $keys as $key ){
                         $piece[$key] = parentChild($arr, $key);
                       }
                     }
                  }else {
                     return $parent; //return the main tag (no kids)
                  }
                  return $piece; // return the array built via recursion
                }
              }
          

          输出:

          Array
          (
              [D] => Array
                  (
                      [G] => Array
                          (
                              [H] => H
                              [F] => F
                          )
          
                      [E] => Array
                          (
                              [A] => A
                              [C] => Array
                                  (
                                      [B] => B
                                  )    
                          )    
                  )    
          )
          

          【讨论】:

            【解决方案8】:

            父子关系嵌套Array
            从数据库中获取所有记录并创建嵌套数组。

            $data = SampleTable::find()->all();
            $tree = buildTree($data);
            print_r($tree);
            
            public function buildTree(array $elements, $parentId = 0) {
                $branch = array();
                foreach ($elements as $element) {
                    if ($element['iParentId'] == $parentId) {
                        $children =buildTree($elements, $element['iCategoriesId']);
                        if ($children) {
                            $element['children'] = $children;
                        }
                        $branch[] = $element;
                    }
                }
                return $branch;
            }
            

            以json格式打印分类和子分类数据

            public static function buildTree(array $elements, $parentId = 0){
                $branch = array();
                foreach($elements as $element){
                    if($element['iParentId']==$parentId){
                        $children =buildTree($elements, $element['iCategoriesId']);
                        if ($children) {
                            $element['children'] = $children;
            
                        }
                            $branch[] = array(
                                'iCategoriesId' => $element->iCategoriesId,
                                'iParentId'=>$element->iParentId,
                                'vCategoriesName'=>$element->vCategoriesName,
                                'children'=>$element->children,
                        );
                    }
                }
                return[
                    $branch
                ];
            }
            

            【讨论】:

              【解决方案9】:
              $tree = array(
                  'H' => 'G',
                  'F' => 'G',
                  'G' => 'D',
                  'E' => 'D',
                  'A' => 'E',
                  'B' => 'C',
                  'C' => 'E',
                  'D' => null,
                  'Z' => null,
                  'MM' =>'Z',
                  'KK' =>'Z',
                  'MMM' =>'MM',
                  // 'MM'=>'DDD'
              );
              

              $aa=$this->parseTree($tree);

              public function get_tress($tree,$key)
              {
              
                  $x=array();
                  foreach ($tree as $keys => $value) {
                      if($value==$key){
                      $x[]=($keys);
                      }
                  }
                  echo "<li>";
                  foreach ($x as $ke => $val) {
                  echo "<ul>";
                      echo($val);
                      $this->get_tress($tree,$val);
                  echo "</ul>";
                  }
                  echo "</li>";
              
              
              }
              function parseTree($tree, $root = null) {
              
                  foreach ($tree as $key => $value) {
                      if($value==$root){
              
                          echo "<ul>";
                          echo($key);
                          $this->get_tress($tree,$key);
                          echo "</ul>";
                      }
                  }
              

              【讨论】:

                【解决方案10】:

                老问题,但我也不得不这样做,递归的例子让我头疼。在我的数据库中,我们有一个locations 表,它是一个loca_id PK(子)和自引用loca_parent_id(父)。

                目的是在 HTML 中表示这种结构。 couyrse 的简单查询可以返回数据是一些固定的顺序,但我发现不足以以自然的方式显示这些数据。我真正想要的是使用 LEVEL 处理 Oracle 树遍历以帮助显示。

                我决定使用“路径”的概念来唯一标识每个条目。例如:

                按路径对数组进行排序应该更容易处理以进行有意义的显示。

                我意识到使用关联数组和排序是作弊,因为它隐藏了操作的递归复杂性,但对我来说这看起来更简单:

                <table>
                <?php
                    
                    $sql = "
                    
                    SELECT l.*,
                           pl.loca_name parent_loca_name,
                           '' loca_path
                    FROM locations l
                    LEFT JOIN locations pl ON l.loca_parent_id = pl.loca_id
                    ORDER BY l.loca_parent_id, l.loca_id
                    
                    ";
                    
                    function print_row ( $rowdata )
                    {
                    ?>
                                      <tr>
                                          <td>
                                              <?=$rowdata['loca_id']?>
                                          </td>
                                          <td>
                                              <?=$rowdata['loca_path']?>
                                          </td>
                                          <td>
                                              <?=$rowdata['loca_type']?>
                                          </td>
                                          <td>
                                              <?=$rowdata['loca_status']?>
                                          </td>
                                      </tr>
                    <?php
                    
                    }
                    
                    $stmt  = $dbh->prepare($sql);
                    $stmt->execute();
                    $result = $stmt->get_result();
                    $data = $result->fetch_all(MYSQLI_ASSOC);
                    
                    $printed = array();
                    
                    // To get tree hierarchy usually means recursion of data.
                    // Here we will try to use an associate array and set a
                    // 'path' value to represent the hierarchy tree in one
                    // pass. Sorting this array by the path value should give
                    // a nice tree order and reference.
                // The array key will be the unique id (loca_id) for each row.
                // The value for each key will the complete row from the database.
                // The row contains a element 'loca_path' - we will write the path
                // for each row here. A child's path will be parent_path/child_name.
                // For any child we encounter with a parent we look up the parents path
                // using the loca_parent_id as the key.
                // Caveat, although tested quickly, just make sure that all parents are
                // returned first by the query.
                    
                    foreach ($data as $row)
                    {
                    
                       if ( $row['loca_parent_id'] == '' ) // Root Parent
                       {
                          $row['loca_path'] = $row['loca_name'] . '/';
                          $printed[$row['loca_id']] = $row;
                       }
                       else // Child/Sub-Parent
                       {
                          $row['loca_path'] = $printed[$row['loca_parent_id']]['loca_path'] . $row['loca_name'] . '/';
                          $printed[$row['loca_id']] = $row;
                       }
                    }
                    
                    // Array with paths built, now sort then print
                    
                    array_multisort(array_column($printed, 'loca_path'), SORT_ASC, $printed);
                    
                    foreach ( $printed as $prow )
                    {
                       print_row ( $prow );
                    }
                    ?>
                    </table>
                

                【讨论】:

                  【解决方案11】:

                  如何创建动态树视图和菜单

                  步骤1:首先我们将在mysql数据库中创建treeview表。此表包含四列。id 是任务 id,name 是任务名称。

                  -
                  -- Table structure for table `treeview_items`
                  --
                  
                  CREATE TABLE IF NOT EXISTS `treeview_items` (
                    `id` int(11) NOT NULL AUTO_INCREMENT,
                    `name` varchar(200) NOT NULL,
                    `title` varchar(200) NOT NULL,
                    `parent_id` varchar(11) NOT NULL,
                    PRIMARY KEY (`id`)
                  ) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
                  
                  --
                  -- Dumping data for table `treeview_items`
                  --
                  
                  INSERT INTO `treeview_items` (`id`, `name`, `title`, `parent_id`) VALUES
                  (1, 'task1', 'task1title', '2'),
                  (2, 'task2', 'task2title', '0'),
                  (3, 'task3', 'task1title3', '0'),
                  (4, 'task4', 'task2title4', '3'),
                  (5, 'task4', 'task1title4', '3'),
                  (6, 'task5', 'task2title5', '5');
                  

                  第二步:树视图递归方法 我在树下创建了 createTreeView() 方法,如果当前任务 ID 大于上一个任务 ID,则该方法调用递归。

                  function createTreeView($array, $currentParent, $currLevel = 0, $prevLevel = -1) {
                  
                  foreach ($array as $categoryId => $category) {
                  
                  if ($currentParent == $category['parent_id']) {                       
                      if ($currLevel > $prevLevel) echo " <ol class='tree'> "; 
                  
                      if ($currLevel == $prevLevel) echo " </li> ";
                  
                      echo '<li> <label for="subfolder2">'.$category['name'].'</label> <input type="checkbox" name="subfolder2"/>';
                  
                      if ($currLevel > $prevLevel) { $prevLevel = $currLevel; }
                  
                      $currLevel++; 
                  
                      createTreeView ($array, $categoryId, $currLevel, $prevLevel);
                  
                      $currLevel--;               
                      }   
                  
                  }
                  
                  if ($currLevel == $prevLevel) echo " </li>  </ol> ";
                  
                  }
                  

                  步骤 3:创建索引文件以显示树视图。 这是树视图示例的主文件,这里我们将调用带有所需参数的 createTreeView() 方法。

                   <body>
                  <link rel="stylesheet" type="text/css" href="_styles.css" media="screen">
                  <?php
                  mysql_connect('localhost', 'root');
                  mysql_select_db('test');
                  
                  
                  $qry="SELECT * FROM treeview_items";
                  $result=mysql_query($qry);
                  
                  
                  $arrayCategories = array();
                  
                  while($row = mysql_fetch_assoc($result)){ 
                   $arrayCategories[$row['id']] = array("parent_id" => $row['parent_id'], "name" =>                       
                   $row['name']);   
                    }
                  ?>
                  <div id="content" class="general-style1">
                  <?php
                  if(mysql_num_rows($result)!=0)
                  {
                  ?>
                  <?php 
                  
                  createTreeView($arrayCategories, 0); ?>
                  <?php
                  }
                  ?>
                  
                  </div>
                  </body>
                  

                  第 4 步:创建 CSS 文件 style.css 在这里,我们将编写所有与 css 相关的类,目前我正在使用订单列表创建树视图。您也可以在此处更改图像路径。

                  img { border: none; }
                  input, select, textarea, th, td { font-size: 1em; }
                  
                  /* CSS Tree menu styles */
                  ol.tree
                  {
                      padding: 0 0 0 30px;
                      width: 300px;
                  }
                      li 
                      { 
                          position: relative; 
                          margin-left: -15px;
                          list-style: none;
                      }
                      li.file
                      {
                          margin-left: -1px !important;
                      }
                          li.file a
                          {
                              background: url(document.png) 0 0 no-repeat;
                              color: #fff;
                              padding-left: 21px;
                              text-decoration: none;
                              display: block;
                          }
                          li.file a[href *= '.pdf']   { background: url(document.png) 0 0 no-repeat; }
                          li.file a[href *= '.html']  { background: url(document.png) 0 0 no-repeat; }
                          li.file a[href $= '.css']   { background: url(document.png) 0 0 no-repeat; }
                          li.file a[href $= '.js']        { background: url(document.png) 0 0 no-repeat; }
                      li input
                      {
                          position: absolute;
                          left: 0;
                          margin-left: 0;
                          opacity: 0;
                          z-index: 2;
                          cursor: pointer;
                          height: 1em;
                          width: 1em;
                          top: 0;
                      }
                          li input + ol
                          {
                              background: url(toggle-small-expand.png) 40px 0 no-repeat;
                              margin: -0.938em 0 0 -44px; /* 15px */
                              height: 1em;
                          }
                          li input + ol > li { display: none; margin-left: -14px !important; padding-left: 1px; }
                      li label
                      {
                          background: url(folder-horizontal.png) 15px 1px no-repeat;
                          cursor: pointer;
                          display: block;
                          padding-left: 37px;
                      }
                  
                      li input:checked + ol
                      {
                          background: url(toggle-small.png) 40px 5px no-repeat;
                          margin: -1.25em 0 0 -44px; /* 20px */
                          padding: 1.563em 0 0 80px;
                          height: auto;
                      }
                          li input:checked + ol > li { display: block; margin: 0 0 0.125em;  /* 2px */}
                          li input:checked + ol > li:last-child { margin: 0 0 0.063em; /* 1px */ }
                  

                  More Details

                  【讨论】:

                    猜你喜欢
                    • 2012-06-01
                    • 2015-08-15
                    • 2019-01-11
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-09-21
                    • 1970-01-01
                    相关资源
                    最近更新 更多