แปลงชุดความสัมพันธ์แม่ลูกให้เป็นแผนผังลำดับชั้นหรือไม่?


101

ฉันมีคู่ชื่อ - ชื่อแม่หลายคู่ซึ่งฉันอยากจะเปลี่ยนเป็นโครงสร้างต้นไม้ที่สืบทอดทางพันธุกรรมให้น้อยที่สุด ตัวอย่างเช่นสิ่งเหล่านี้อาจเป็นการจับคู่:

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

ซึ่งจำเป็นต้องเปลี่ยนเป็น (a) heirarchical tree (s):

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

ผลลัพธ์สุดท้ายที่ฉันต้องการคือชุดของ<ul>องค์ประกอบที่ซ้อนกันโดยแต่ละ<li>องค์ประกอบมีชื่อของเด็ก

ไม่มีความไม่สอดคล้องกันในการจับคู่ (ลูกคือพ่อแม่ของตัวเองผู้ปกครองคือลูกของเด็ก ฯลฯ ) ดังนั้นจึงสามารถทำการเพิ่มประสิทธิภาพได้หลายอย่าง

ใน PHP ฉันจะเปลี่ยนจากอาร์เรย์ที่มีลูก => คู่พาเรนต์เป็นชุดของ Nested <ul>s ได้อย่างไร

ฉันรู้สึกว่ามีส่วนเกี่ยวข้องกับการเรียกซ้ำ แต่ฉันยังไม่ตื่นพอที่จะคิดผ่าน

คำตอบ:


129

สิ่งนี้ต้องการฟังก์ชันการเรียกซ้ำขั้นพื้นฐานเพื่อแยกวิเคราะห์คู่ลูก / พาเรนต์กับโครงสร้างแบบทรีและฟังก์ชันเรียกซ้ำอีกฟังก์ชันหนึ่งเพื่อพิมพ์ออกมา ฟังก์ชั่นเดียวเท่านั้นที่จะเพียงพอ แต่นี่คือสองเพื่อความชัดเจน (ฟังก์ชันรวมสามารถพบได้ในตอนท้ายของคำตอบนี้)

เริ่มต้นอาร์เรย์ของคู่ลูก / พาเรนต์ก่อน:

$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 ครั้งในชุดข้อมูลที่มีขนาดเล็กเท่านี้ แต่ในชุดที่ใหญ่กว่านั้นอาจสร้างความแตกต่างได้


2
Tatu. ฉันจะเปลี่ยนฟังก์ชัน printTree ไม่ให้สะท้อน html ของต้นไม้โดยตรงได้อย่างไร แต่บันทึก html เอาต์พุตทั้งหมดลงในตัวแปรและส่งคืน ขอบคุณ
Enrique

สวัสดีฉันคิดว่าการประกาศฟังก์ชันต้องเป็น parseAndPrintTree ($ tree, $ root = null) และการเรียกซ้ำจะเป็น parseAndPrintTree ($ child, $ tree); ขอแสดงความนับถือ
razor7

55

อีกฟังก์ชันในการสร้างต้นไม้ (ไม่มีการเรียกซ้ำที่เกี่ยวข้องใช้การอ้างอิงแทน):

$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 เป็นค่าใช้จ่ายในการรับรู้ที่รุนแรงในการแยกวิเคราะห์ฟังก์ชัน / การโทรซ้ำ หากไม่มีการจัดสรรสแต็กอย่างชัดเจนฉันจะทำซ้ำในการเรียกซ้ำทุกวัน


29

อีกวิธีหนึ่งที่ง่ายกว่าในการแปลงโครงสร้างแบบแบนใน$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: นำโมเดลชุดที่ซ้อนกันไปไว้ในทรี<ul>ย่อย“ ปิด” แต่ซ่อนไว้ ที่คำตอบแสดงให้เห็นเช่นกันว่าด้วยตัวทำซ้ำคุณค่อนข้างยืดหยุ่น

อย่างไรก็ตามนี่เป็นรายการที่จัดเรียงไว้ล่วงหน้าดังนั้นจึงไม่เหมาะกับตัวอย่างของคุณ นอกจากนี้ฉันต้องการแก้ปัญหานี้เสมอสำหรับโครงสร้างแบบต้นไม้มาตรฐานและ HTML <ul>และ<li>องค์ประกอบ

แนวคิดพื้นฐานที่ฉันคิดขึ้นมีดังต่อไปนี้:

  1. TreeNode- บทคัดย่อแต่ละองค์ประกอบเป็นTreeNodeประเภทง่ายๆที่สามารถให้คุณค่า (เช่นName) และมีลูกหรือไม่
  2. TreeNodesIterator- เป็นRecursiveIteratorที่สามารถย้ำกว่าชุด (อาร์เรย์) TreeNodesเหล่านี้ มันค่อนข้างง่ายเพราะTreeNodeประเภทรู้อยู่แล้วว่ามีลูกหรือไม่และคนไหน
  3. RecursiveListIterator- A RecursiveIteratorIteratorที่มีเหตุการณ์ทั้งหมดที่จำเป็นเมื่อมันวนซ้ำซ้ำไปซ้ำมาทุกประเภทRecursiveIterator :
    • beginIteration / endIteration - จุดเริ่มต้นและจุดสิ้นสุดของรายการหลัก
    • beginElement / endElement - จุดเริ่มต้นและจุดสิ้นสุดของแต่ละองค์ประกอบ
    • beginChildren/ endChildren- จุดเริ่มต้นและจุดสิ้นสุดของรายชื่อเด็กแต่ละคน สิ่งนี้RecursiveListIteratorจัดเตรียมเฉพาะเหตุการณ์เหล่านี้ในรูปแบบของการเรียกใช้ฟังก์ชัน รายการเด็กตามปกติสำหรับ<ul><li>รายการจะเปิดและปิดภายใน<li>องค์ประกอบหลัก ดังนั้น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ที่ห่อ<ul>และ<li>องค์ประกอบและและกำลังตัดสินใจว่าจะส่งออกโครงสร้างรายการอย่างไร:

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

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>

การสาธิต (ตัวแปร PHP 5.2)

ตัวแปรที่เป็นไปได้จะเป็นตัววนซ้ำที่วนซ้ำRecursiveIteratorและให้การวนซ้ำสำหรับเหตุการณ์ทั้งหมดที่สามารถเกิดขึ้นได้ สวิตช์ / เคสภายใน foreach loop สามารถจัดการกับเหตุการณ์ได้

ที่เกี่ยวข้อง:


3
"ได้รับการออกแบบมาเป็นอย่างดี" เช่นเดียวกับวิธีแก้ปัญหานี้ "วิธีที่ง่ายกว่า" กว่าตัวอย่างก่อนหน้านี้เป็นอย่างไร - ดูเหมือนว่าจะเป็นวิธีแก้ปัญหาที่ได้รับการออกแบบมาเป็นอย่างดีสำหรับปัญหาเดียวกัน
Andre

@Andre: ตามเกรดของการห่อหุ้ม IIRC ในคำตอบอื่นที่เกี่ยวข้องฉันมีส่วนของโค้ดที่ไม่ได้ห่อหุ้มอย่างสมบูรณ์ซึ่งมีขนาดเล็กกว่ามากและอาจ "ง่ายกว่า" ขึ้นอยู่กับ POV
hakre

@hakre ฉันจะแก้ไขคลาส "ListDecorator" เพื่อเพิ่ม 'id' ให้กับ LI ได้อย่างไรซึ่งจะถูกดึงมาจาก Tree Array
Gangesh

1
@Gangesh: ง่ายที่สุดด้วยโหนด vistor ^^ ล้อเล่นเล็กน้อยตรงไปตรงมาคือการขยายมัณฑนากรและแก้ไข beginElement () รับตัววนซ้ำภายใน (ดูตัวอย่างวิธีการ inset ()) และการทำงานกับแอตทริบิวต์ id
hakre

@hakre ขอบคุณ ฉันจะลองดู
Gangesh

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

5

แม้ว่าวิธีการแก้ปัญหาของAlexander-Konstantinovอาจดูไม่ง่ายเท่าที่ควรในตอนแรก แต่ก็เป็นทั้งอัจฉริยะและดีกว่าในแง่ของประสิทธิภาพอย่างมาก แต่ก็ควรได้รับการโหวตให้เป็นคำตอบที่ดีที่สุด

ขอบคุณเพื่อนฉันสร้างเกณฑ์มาตรฐานเพื่อเป็นเกียรติแก่คุณเพื่อเปรียบเทียบ 2 โซลูชันที่นำเสนอในโพสต์นี้

ฉันมีต้นไม้แบน @ 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)

2

ในการแยกวิเคราะห์เป็น ULs และ 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>';
}

แต่ฉันชอบที่จะเห็นโซลูชันที่ไม่ต้องการให้คุณทำซ้ำผ่านอาร์เรย์บ่อยๆ ...


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

1

ความสัมพันธ์ระหว่างแม่ลูกที่ซ้อนกัน 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
    ];
}

0
$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 (ต้นไม้ $);

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

0

คำถามเก่า แต่ฉันก็ต้องทำเช่นนี้เช่นกันและตัวอย่างที่มีการเรียกซ้ำทำให้ฉันปวดหัว ในฐานข้อมูลของฉันเรามีlocationsตารางซึ่งเป็นloca_idPK (Child) และการอ้างอิงตัวเองloca_parent_id (Parent)

จุดมุ่งหมายคือการแสดงโครงสร้างนี้ใน HTML คิวรีอย่างง่ายของ couyrse สามารถส่งคืนข้อมูลเป็นคำสั่งคงที่ แต่ฉันพบว่าไม่ดีพอที่จะแสดงข้อมูลดังกล่าวด้วยวิธีที่เป็นธรรมชาติ สิ่งที่ฉันต้องการจริงๆคือการจัดการ Oracle Tree Walk ด้วยLEVELเพื่อช่วยในการแสดงผล

ฉันตัดสินใจใช้แนวคิดเรื่อง 'เส้นทาง' เพื่อระบุรายการแต่ละรายการโดยไม่ซ้ำกัน ตัวอย่างเช่น:

การจัดเรียงอาร์เรย์ตามเส้นทางควรทำให้ง่ายต่อการประมวลผลสำหรับการแสดงผลที่มีความหมาย

ฉันตระหนักดีว่าการใช้อาร์เรย์และประเภทเชื่อมโยงเป็นการโกงเนื่องจากมันซ่อนความซับซ้อนซ้ำซากของการดำเนินการ แต่สำหรับฉันแล้วสิ่งนี้ดูง่ายกว่า:

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

-1

วิธีสร้างมุมมองต้นไม้แบบไดนามิกและเมนู

ขั้นตอนที่ 1: ขั้นแรกเราจะสร้างตาราง Treeview ในฐานข้อมูล mysql ตารางนี้มีสี่คอลัมน์ id คือ id งานและชื่อคือชื่องาน

-
-- 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: วิธีการเรียกซ้ำแบบ Tree view ที่ฉันได้สร้างไว้ด้านล่างวิธีการ 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: สร้างไฟล์ดัชนีเพื่อแสดงมุมมองแบบต้นไม้ นี่คือไฟล์หลักของตัวอย่าง 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 */ }

รายละเอียดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.