簡體   English   中英

PHP - 如何建立樹狀結構列表?

[英]PHP - How to build tree structure list?

所以,我的問題是,我想構建這兩個表的樹:

Parent table:
+-------+---------------+
| pr_id |  parent_name  |
+-------+---------------+
|   1   |       p       |
|   2   |      p_0      | 
|   3   |     p_0_1     | 
|   4   |       q       | 
+-------+---------------+

Child table:
+-------+---------------+---------------------------+
| ch_id |     pr_id     |        child_name         |
+-------+---------------+---------------------------+
|   1   |       1       |            p_0            |
|   2   |       1       |            p_1            |
|   3   |       2       |           p_0_0           |
|   4   |       2       |           p_0_1           |
|   5   |       3       |          p_0_1_0          |
|   6   |       3       |          p_0_1_1          |
|   7   |       4       |            q_0            |
|   8   |       4       |            q_1            |
+-------+---------------+---------------------------+

樹應該看起來像:

  • p
    • p_0
      • p_0_0
      • p_0_1
        • p_0_1_0
        • p_0_1_1
  • q

任何人都可以幫助我解決遞歸問題嗎?

您不需要在數據庫中創建 2 個表,您可以僅從一個表中維護它,如下所示

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

算法非常簡單:

  1. 獲取所有元素的數組和當前父級的 id(最初為 0/nothing/null/whatever)。
  2. 循環遍歷所有元素。
  3. 如果元素的 parent_id 與您在 1. 中獲得的當前父 ID 匹配,則該元素是父元素的子元素。 把它放在你當前孩子的列表中(這里:$branch)。
  4. 使用您剛剛在 3. 中標識的元素的 id 遞歸調用該函數,即找到該元素的所有子元素,並將它們添加為子元素。
  5. 返回您找到的孩子的名單。

多循環和/或遞歸很簡單,但性能方面不是最佳解決方案。 它們對於較小的數據集完全沒問題,但這些規模不是很好。

更好的解決方案是每個項目只處理一次。

基本思想是您創建對象並使用對象的 byReference 特性。 為此,我創建了將所有內容“粘合”在一起的“ShadowObjects”,這些“ShadowObjects”稍后會填充數據。

這是一個例子

你有一個通用的idparentId列表

$list = [
    ['id' => 1, 'parent' => 0, 'otherData' => 'a'],
    ['id' => 2, 'parent' => 1, 'otherData' => 'b'],
    ['id' => 3, 'parent' => 1, 'otherData' => 'c'],
    ['id' => 4, 'parent' => 3, 'otherData' => 'd'],
    ['id' => 5, 'parent' => 3, 'otherData' => 'e'],
    ['id' => 6, 'parent' => 4, 'otherData' => 'f'],
    ['id' => 7, 'parent' => 1, 'otherData' => 'g'],
];

樹 Class

我們需要一個元素來保存和索引我們的樹元素,我喜歡為此使用 TreeClass。 class 將所有 TreeItems 保存在一個列表中,以唯一 ID 作為標識符,唯一 ID 對於快速訪問每個項目很重要。

我也喜歡 getRootItems() 方法來獲取所有 Root_Tree_Elements

class Tree
{
    private array $items = [];
    
    public function getItem(int $id): ?TreeItem
    {
        return $this->items[$id]??null;
    }
    
    public function setItem(TreeItem $item, int $id): void
    {
        $this->items[$id] = $item;
    }
    
    public function getRootItems(): array
    {
        $rootItems = [];
        foreach ($this->items as $item) {
            if ($item->getParent() === null)
                $rootItems[] = $item;
        }
        
        return $rootItems;
    }
}

樹項目

是一個 object 代表一個“樹元素”它保存實際數據和父/子關系

class TreeItem 
{
    private ?TreeItem $parent = null;
    private array $children = [];
    private array $data = [];
    
    public function setParent(?TreeItem $parent): void
    {
        $this->parent = $parent;
    }
    
    public function addChild(TreeItem $child): void
    {
        $this->children[] = $child;
    }
    
    public function setData(array $data): void
    {
        $this->data = $data;
    }
}

處理循環

這是創建 TreeStructure 的單個循環:

從列表中,我們知道存在具有parent_ID X的父項以及ID Y的項是它的子項的信息。

所以第一件事是我們詢問我們的樹是否已經存在 ID X 的 TreeItem(僅當 ID 不是 0 或 NULL 或“無效”時)。 如果沒有找到,我們創建這個樹項 NEW 並將其添加到樹中,即使我們不知道父項的所有數據,我們只知道父項的 ID,但這足以作為“SHADOW_ITEM”。

之后,我們查找實際的 TreeItem。 我們擁有該元素的所有信息。 我們查找它,因為我們有可能已經將它創建為“ShadowItem”,所以我們可以用實際的數據填充它。 請記住,TreeItem 和 Parent 是同一類型的對象。

同樣,如果 Item 不存在,則新建它並將新 Item 添加到 TreeList。 還將 ParentTreeItem 添加到當前 TreeItem。

然后我們用新的孩子填充我們的父元素(雙向關系父母知道孩子/孩子知道父母)。

我們不需要擔心將錯誤的孩子添加到父母甚至重復,因為我們只處理每個項目一次。

最后,我們將實際數據填充到每個項目。 因此,總共每個 ShadowItem 都包含真實數據,並且會丟失 ShadowItem 的 state。

$tree = new Tree();
foreach($list as $itemData) {
    
    $id = $itemData['id'];
    $pid = $itemData['parent'];
    
    // if ZERO we have root element no parent exists
    
    $parent = $tree->getItem($pid);
    if ($pid > 0 && $parent === null) {
        $parent = new TreeItem();
        $tree->setItem($parent, $pid);
    }
    
    // create new tree item if not exists / otherwise get existing item
    $item = $tree->getItem($id);
    if ($item === null) {
        $item = new TreeItem();
        $item->setParent($parent);
        $tree->setItem($item, $id);
    }
    
    if ($parent !== null) {
        $parent->addChild($item);
    }
    
    $item->setData($itemData);
}

這是一個示例,並且“未優化”以使其更易於理解。 Tree 和 TreeItem 上還缺少許多東西以使實際可用,

隨意添加您喜歡的所有方法。

例如:

  • 刪除所有無效的剩余陰影元素(如果樹結構數據損壞,項目指向不再存在的父項,則可能發生這種情況)
  • 或 TreeItem 方法,為您提供此數據和所有子項的有序數組結構(類似 __toArray())。

我經常在菜單生成中使用這種邏輯,在某處“緩存”/“存儲”完成樹也很容易。 一個簡單的序列化/反序列化就可以了:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM