2010-05-26 67 views
87

我有一堆名字 - 父母對,我想變成儘可能少的陰莖樹結構。因此,例如,這些可能是配對:將一系列父子關係轉換爲分層樹?

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

需要被轉化成(一)heirarchical樹(S):

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

,我想最終的結果是一組嵌套<ul>元素,每個<li>包含孩子的名字。

配對中沒有不一致(孩子是自己的父母,父母是孩子的孩子等),所以可能會進行一些優化。

在PHP中,我將如何從包含child =>父對的數組轉換爲一組嵌套的<ul>

我有一種感覺,涉及遞歸,但我沒有足夠清醒的思考。

回答

113

這需要一個非常基本的遞歸函數來將子對/父對解析爲樹結構,並使用另一個遞歸函數將其打印出來。只有一個函數就足夠了,但爲了清楚起見,這裏有兩個函數(在這個答案的最後可以找到一個組合函數)。

首先初始化父/子對的數組:

$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次迭代,但是在更大的數據集上可能會有所不同。

+1

大肚。我怎樣才能改變printTree函數不直接回應樹的html,但保存所有的輸出html到一個變量並返回它?謝謝 – Enrique 2012-12-17 13:58:59

+0

嗨,我認爲函數聲明必須是parseAndPrintTree($ tree,$ root = null) 並且遞歸調用應該是parseAndPrintTree($ child,$ tree); 致以問候 – razor7 2014-04-25 20:52:02

2

好,解析成上行線路及LIS,這將是這樣的:

$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>'; 
} 

但我很想看到,不要求你通過數組所以經常要迭代的解決方案.. 。

8

首先,我會變成鍵值對的直數組分級陣列

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

這就是我想出了:

$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 
         )  
       )  
     )  
) 
48

然而,另一個函數來使樹(沒有遞歸參與,使用的引用代替):

$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() 
     ) 
     ... 
    ) 
) 

其中ca使用遞歸函數可以很容易地將其打印爲HTML列表。

+0

+1 - 非常聰明。儘管我發現遞歸解決方案更合乎邏輯。但我更喜歡你的函數的輸出格式。 – Eric 2010-05-26 20:20:38

26

另一種更簡化的方式將$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 <ul> but hiding 「closed」 subtreesanswer顯示,與迭代器,你很靈活。

但是這是一個預先排序的列表,所以不適合您的示例。此外,我一直想解決這個問題的一種標準樹結構和HTML的<ul><li>元素。

我想出的基本概念是:

  1. TreeNode - 文摘每個元素成簡單TreeNode類型,可以提供它的值(例如Name),以及是否有孩子。
  2. TreeNodesIterator - A RecursiveIterator能夠遍歷這些TreeNodes的集合(數組)。這很簡單,因爲TreeNode類型已經知道它是否有孩子和哪些孩子。
  3. RecursiveListIterator - 有當遞歸遍歷任何一種RecursiveIterator所需的所有事件RecursiveIteratorIterator
    • beginIteration/endIteration - 開始和主列表的末尾。
    • beginElement/endElement - 每個元素的開始和結束。
    • beginChildren/endChildren - 每個子列表的開始和結束。 這個RecursiveListIterator只以函數調用的形式提供這些事件。兒童名單,因爲它是典型的<ul><li>名單,被打開和關閉它的父母<li>元素。因此endElement事件在相應的endChildren事件之後被觸發。這可以改變或可配置,以擴大使用這個類。這些事件是作爲函數調用分發給裝飾器對象的,然後將事情分開。
  4. ListDecorator - A 「裝飾」 類,它僅僅是一個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()); 
} 

首先,讓我們看看,簡單地包裝了<ul><li>元素,並在判決的表結構如何輸出的ListDecorator

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

TreeNodesIteratorRecursiveIterator,使遞歸迭代過第e單一$root節點。它作爲一個數組傳遞,因爲該類需要迭代並允許與一組子元素重用,這也是一個元素數組。

$rit = new RecursiveListIterator($it); 

RecursiveListIteratorRecursiveIteratorIterator提供上述事件。爲了利用它,可以僅設置一個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()); 
} 

Outpupt:

<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循環內的開關/情況可以處理事件。

相關:

+2

由於這個解決方案的「精心設計」 - 與前面的例子相比,「更簡化的方式」究竟如何 - 它看起來像是針對同一問題的過度設計的解決方案 – Andre 2014-11-09 10:10:31

+0

@Andre:通過封裝IIRC的等級。在另一個相關的答案中,我有一個完全沒有封裝的代碼片段,這個代碼片段要小得多,因此可能會根據POV「更簡化」。 – hakre 2015-09-03 11:39:25

+0

@hakre如何修改「ListDecorator」類以將「id」添加到LI,即從樹數組中獲取? – Gangesh 2016-06-24 20:02:47

0

如何創建動態樹視圖和菜單

第1步:首先,我們將在MySQL數據庫中創建樹狀表。此表包含四個column.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'); 

步驟2:樹視圖遞歸方法 我已低於該調用遞歸如果當前的任務id爲大於先前任務ID更大樹createTreeView()方法創建。

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步:創建索引文件以顯示樹視圖。 這是treeview示例的主要文件,在這裏我們將調用具有所需參數的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

4

雖然Alexander-Konstantinov的解決方案可能似乎不容易在第一次讀,它既是天才,在性能方面成倍更好,這應該被選爲最佳答案。

感謝隊友,我做了一個基準,以您的榮譽來比較本文中介紹的兩種解決方案。

我有一個@ 250k的扁平樹,有6個層次,我必須轉換,我正在尋找更好的方法來避免遞歸迭代。

遞歸VS參考:

// 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)