Chuyển một loạt các mối quan hệ cha-con thành một cây phân cấp?


100

Tôi có một loạt các cặp tên-cha mẹ, mà tôi muốn chuyển thành càng ít cấu trúc cây thứ cấp càng tốt. Vì vậy, ví dụ, đây có thể là các cặp:

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

Cần chuyển đổi thành (a) cây thứ bậc:

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

Kết quả cuối cùng mà tôi muốn là một tập hợp các <ul>phần tử lồng nhau , với mỗi phần tử <li>chứa tên của đứa trẻ.

Không có sự mâu thuẫn nào trong các cặp (con là cha mẹ của chính nó, cha mẹ là con của con, v.v.), vì vậy có thể có một loạt các tối ưu hóa.

Làm thế nào, trong PHP, tôi sẽ đi từ một mảng chứa các cặp con => cha, sang một tập hợp các <ul>s lồng nhau ?

Tôi có cảm giác rằng đệ quy có liên quan, nhưng tôi không đủ tỉnh táo để suy nghĩ thấu đáo.

Câu trả lời:


129

Điều này yêu cầu một hàm đệ quy rất cơ bản để phân tích cú pháp cặp con / mẹ thành cấu trúc cây và một hàm đệ quy khác để in nó ra. Chỉ một hàm là đủ nhưng đây là hai để rõ ràng hơn (có thể tìm thấy một hàm kết hợp ở cuối câu trả lời này).

Đầu tiên khởi tạo mảng các cặp con / cha:

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

Sau đó, hàm phân tích mảng đó thành một cấu trúc cây phân cấp:

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

Và một hàm đi qua cây đó để in ra một danh sách không có thứ tự:

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

Và cách sử dụng thực tế:

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

Đây là nội dung của $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
                            )
                        )
                    )
                )
            )
        )
    )
)

Nếu bạn muốn hiệu quả hơn một chút, bạn có thể kết hợp các hàm đó thành một và giảm số lần lặp lại:

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

Bạn sẽ chỉ lưu 8 lần lặp trên một tập dữ liệu nhỏ như thế này nhưng trên các tập lớn hơn, nó có thể tạo ra sự khác biệt.


2
Tatu. Làm cách nào để thay đổi hàm printTree để không lặp lại trực tiếp html của cây mà lưu tất cả html đầu ra vào một biến và trả về? cảm ơn
Enrique

Xin chào, tôi nghĩ rằng khai báo hàm phải là parseAndPrintTree ($ tree, $ root = null) và lệnh gọi đệ quy sẽ là parseAndPrintTree ($ child, $ tree); Trân trọng
razor7

55

Tuy nhiên, một chức năng khác để tạo một cây (không liên quan đến đệ quy, sử dụng tham chiếu thay thế):

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

Trả về một mảng phân cấp như thế này:

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

Có thể dễ dàng in ra dưới dạng danh sách HTML bằng cách sử dụng hàm đệ quy.


+1 - Rất thông minh. Mặc dù tôi thấy giải pháp đệ quy hợp lý hơn. Nhưng tôi thích định dạng đầu ra của hàm của bạn hơn.
Eric

@Eric hợp lý hơn? Tôi có ý kiến ​​khác. Không có gì là 'logic' trong đệ quy; có OTOH một chi phí nhận thức nghiêm trọng về phân tích cú pháp các hàm / cuộc gọi đệ quy. Nếu không có phân bổ ngăn xếp rõ ràng, tôi sẽ thực hiện lặp lại đệ quy mỗi ngày.


29

Một cách khác, đơn giản hơn để chuyển đổi cấu trúc phẳng trong $treethành một hệ thống phân cấp. Chỉ cần một mảng tạm thời để hiển thị nó:

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

Đó là tất cả để đưa hệ thống phân cấp vào một mảng đa chiều:

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
)

Đầu ra ít tầm thường hơn nếu bạn muốn tránh đệ quy (có thể là một gánh nặng với các cấu trúc lớn).

Tôi luôn muốn giải quyết "tình huống khó xử" UL / LI khi xuất ra một mảng. Vấn đề nan giải là mỗi mục không biết có trẻ em theo dõi hay không hoặc cần phải đóng bao nhiêu phần tử đứng trước. Trong một câu trả lời khác, tôi đã giải quyết vấn đề đó bằng cách sử dụng a RecursiveIteratorIteratorvà tìm kiếm getDepth()và các thông tin meta khác mà chính tôi đã viết Iteratorcung cấp: Đưa mô hình tập hợp lồng nhau vào một <ul>nhưng ẩn cây con “đóng” . Câu trả lời đó cũng cho thấy rằng với trình vòng lặp, bạn khá linh hoạt.

Tuy nhiên, đó là danh sách được sắp xếp trước, vì vậy sẽ không phù hợp với ví dụ của bạn. Ngoài ra, tôi luôn muốn giải quyết vấn đề này cho một loại cấu trúc cây tiêu chuẩn và HTML <ul><li>các phần tử.

Khái niệm cơ bản tôi đã đưa ra như sau:

  1. TreeNode- Tóm tắt từng phần tử thành một TreeNodekiểu đơn giản có thể cung cấp giá trị của nó (ví dụ Name) và liệu nó có con hay không.
  2. TreeNodesIterator- A RecursiveIteratorcó thể lặp lại trên một tập hợp (mảng) trong số này TreeNodes. Điều đó khá đơn giản vì TreeNodekiểu này đã biết nó có con nào không và con nào.
  3. RecursiveListIterator- A RecursiveIteratorIteratorcó tất cả các sự kiện cần thiết khi nó lặp lại đệ quy trên bất kỳ loại nào RecursiveIterator:
    • beginIteration/ endIteration- Bắt đầu và kết thúc danh sách chính.
    • beginElement/ endElement- Bắt đầu và kết thúc mỗi phần tử.
    • beginChildren/ endChildren- Bắt đầu và kết thúc mỗi danh sách trẻ em. Điều này RecursiveListIteratorchỉ cung cấp các sự kiện này dưới dạng lời gọi hàm. danh sách con, vì nó là điển hình cho <ul><li>danh sách, được mở và đóng bên trong <li>phần tử mẹ của nó . Do đó endElementsự kiện được kích hoạt sau endChildrensự kiện theo . Điều này có thể được thay đổi hoặc thực hiện cấu hình để mở rộng việc sử dụng lớp này. Sau đó, các sự kiện được phân phối dưới dạng lời gọi hàm đến một đối tượng decorator, để giữ cho mọi thứ khác biệt.
  4. ListDecorator- Một lớp "decorator" chỉ là người nhận các sự kiện của RecursiveListIterator.

Tôi bắt đầu với logic đầu ra chính. Lấy $treemảng hiện có phân cấp , mã cuối cùng trông giống như sau:

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

Trước tiên hãy nhìn vào ListDecoratormà chỉ đơn giản kết thúc tốt đẹp <ul><li>các yếu tố và là quyết định về cách thức cấu trúc danh sách là kết quả:

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

Hàm khởi tạo lấy trình lặp danh sách mà nó đang làm việc. insetchỉ là một chức năng trợ giúp để thụt lề đầu ra đẹp mắt. Phần còn lại chỉ là các hàm đầu ra cho mỗi sự kiện:

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

Với các chức năng đầu ra này, đây là vòng lặp / kết thúc đầu ra chính một lần nữa, tôi sẽ đi qua từng bước:

$root = new TreeNode($tree);

Tạo thư mục gốc TreeNodesẽ được sử dụng để bắt đầu lặp lại:

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

Đây TreeNodesIteratorlà một RecursiveIteratorcho phép lặp lại đệ quy trên một $rootnút. Nó được truyền dưới dạng một mảng vì lớp đó cần một thứ gì đó để lặp lại và cho phép sử dụng lại với một tập hợp con cũng là một mảng các TreeNodephần tử.

$rit = new RecursiveListIterator($it);

Đây RecursiveListIteratorlà một RecursiveIteratorIteratorcung cấp các sự kiện đã nói. Để sử dụng nó, chỉ ListDecoratorcần cung cấp một (lớp ở trên) và được gán với addDecorator:

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

Sau đó, mọi thứ được thiết lập foreachtrên nó và xuất ra từng nút:

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

Như ví dụ này cho thấy, toàn bộ logic đầu ra được đóng gói trong ListDecoratorlớp và đơn này foreach. Toàn bộ quá trình duyệt đệ quy đã được đóng gói đầy đủ vào các trình vòng lặp đệ quy SPL cung cấp một thủ tục xếp chồng lên nhau, có nghĩa là nội bộ không có lệnh gọi hàm đệ quy nào được thực hiện.

Sự kiện dựa trên ListDecoratorcho phép bạn sửa đổi đầu ra cụ thể và cung cấp nhiều loại danh sách cho cùng một cấu trúc dữ liệu. Thậm chí có thể thay đổi đầu vào vì dữ liệu mảng đã được đóng gói TreeNode.

Ví dụ về mã đầy đủ:

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

Ra ngoài:

<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 (biến thể PHP 5.2)

Một biến thể có thể có sẽ là một trình lặp lặp lại bất kỳ RecursiveIteratorvà cung cấp một phép lặp trên tất cả các sự kiện có thể xảy ra. Sau đó, một công tắc / trường hợp bên trong vòng lặp foreach có thể giải quyết các sự kiện.

Có liên quan:


3
Như "cũng thiết kế" như giải pháp này là - làm thế nào chính xác là nó "hơn đơn giản hóa cách" so với ví dụ trước - Nó chỉ có vẻ như một giải pháp trong thiết kế để cùng một vấn đề
Andre

@Andre: Theo cấp độ đóng gói IIRC. Trong một câu trả lời liên quan khác, tôi có một đoạn mã hoàn toàn không được đóng gói nhỏ hơn nhiều và do đó có thể được "đơn giản hóa" hơn tùy thuộc vào POV.
hakre

@hakre Làm cách nào để sửa đổi lớp "ListDecorator" để thêm 'id' vào LI, đang được tìm nạp từ mảng cây?
Gangesh

1
@Gangesh: Dễ dàng nhất với vistor nút. ^^ Đùa một chút, về phía trước là mở rộng trình trang trí và chỉnh sửa beginElement (), lấy trình lặp bên trong (xem ví dụ phương thức inset ()) và tác phẩm với thuộc tính id.
hakre

@hakre Cảm ơn. Tôi sẽ thử điều đó.
Gangesh

8

Vâng, trước tiên, tôi sẽ biến mảng thẳng các cặp khóa-giá trị thành một mảng phân cấp

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

Điều đó sẽ có thể chuyển đổi một mảng phẳng với parent_id và id thành một mảng phân cấp:

$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(),
                ),
            ),
        ),
    ),
);

Sau đó, chỉ cần tạo một hàm kết xuất:

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

Mặc dù giải pháp của Alexander-Konstantinov thoạt đầu có vẻ không dễ đọc, nhưng nó vừa là thiên tài vừa tốt hơn theo cấp số nhân về mặt hiệu suất, điều này lẽ ra phải được bình chọn là câu trả lời hay nhất.

Cảm ơn người bạn đời, tôi đã thực hiện một điểm chuẩn để bạn so sánh 2 giải pháp được trình bày trong bài đăng này.

Tôi đã có một cây phẳng @ 250k với 6 cấp độ mà tôi phải chuyển đổi và tôi đang tìm kiếm cách tốt hơn để làm như vậy và tránh lặp lại đệ quy.

Đệ quy so với Tham chiếu:

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

Đầu ra nói cho chính nó:

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

2

Chà, để phân tích cú pháp thành UL và LI, nó sẽ như sau:

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

Nhưng tôi muốn thấy một giải pháp không yêu cầu bạn phải lặp lại mảng thường xuyên ...


2

Đây là những gì tôi nghĩ ra:

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

kết quả đầu ra:

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

            [E] => Array
                (
                    [A] => A
                    [C] => Array
                        (
                            [B] => B
                        )    
                )    
        )    
)

1

Mối quan hệ cha-con Mảng lồng nhau
Tìm nạp tất cả bản ghi từ cơ sở dữ liệu và tạo mảng lồng nhau.

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

In dữ liệu Danh mục và Danh mục con ở Định dạng 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 ($ 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>";
        }
    }

0

Câu hỏi cũ, nhưng tôi cũng phải làm điều này và các ví dụ với đệ quy khiến tôi đau đầu. Trong cơ sở dữ liệu của tôi, chúng tôi có một locationsbảng, đó là loca_idPK (Con) và tự tham chiếu loca_parent_id(Parent).

Mục đích là để biểu diễn cấu trúc này trong HTML. Truy vấn đơn giản có thể trả về dữ liệu là một số thứ tự cố định nhưng tôi thấy không đủ tốt để hiển thị dữ liệu như vậy theo cách tự nhiên. Những gì tôi thực sự muốn là xử lý bước đi trên cây của Oracle LEVELđể giúp hiển thị.

Tôi quyết định sử dụng ý tưởng về một 'con đường' để xác định duy nhất từng mục nhập. Ví dụ:

Sắp xếp mảng theo đường dẫn sẽ giúp xử lý dễ dàng hơn để hiển thị có ý nghĩa.

Tôi nhận ra rằng việc sử dụng các mảng và sắp xếp kết hợp là gian lận vì nó che giấu sự phức tạp đệ quy của các phép toán, nhưng với tôi điều này trông đơn giản hơn:

<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

Cách tạo Menu và Chế độ xem dạng cây động

Bước 1: Đầu tiên chúng ta sẽ Tạo bảng treeview trong cơ sở dữ liệu mysql. bảng này chứa bốn column.id là id nhiệm vụ và name là tên tác vụ.

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

Bước 2: Phương thức đệ quy dạng cây Tôi đã tạo bên dưới phương thức tree createTreeView () gọi đệ quy nếu id tác vụ hiện tại lớn hơn id tác vụ trước.

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

}

Bước 3: Tạo tệp chỉ mục để hiển thị dạng cây. Đây là tệp chính của ví dụ treeview ở đây chúng ta sẽ gọi phương thức createTreeView () với các tham số bắt buộc.

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

Bước 4: Tạo file CSS style.css Ở đây chúng ta sẽ viết tất cả các class liên quan đến css, hiện tại mình đang sử dụng order list để tạo tree view. bạn cũng có thể thay đổi đường dẫn hình ảnh tại đây.

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 */ }

Thêm chi tiết

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.