繁体   English   中英

遍历一棵树,其中子节点的数组可以是任意深度

[英]Looping through a tree where array of child nodes may be any depth

我正在使用jstree进行项目,并尝试将其保存到数据库中。

我很难理解如何执行此操作,因为用户可以根据需要创建任意数量的节点,并且深度不限。

例如,考虑以下树:

在此处输入图片说明

将其发布到PHP时,数组如下。 注意children元素如何出现:

    $tree[0]['id'] = 'loc1';
    $tree[0]['text'] = 'Sector';

    $tree[1]['id'] = 'loc2';
    $tree[1]['text'] = 'Location';
    $tree[1]['children'][0]['id'] = 'italy-1';
    $tree[1]['children'][0]['text'] = 'Italy';
    $tree[1]['children'][1]['id'] = 'poland-1';
    $tree[1]['children'][1]['text'] = 'Poland';

    $tree[2]['id'] = 'j1_1';
    $tree[2]['text'] = 'abc';
    $tree[2]['children'][0]['id'] = 'j1_2';
    $tree[2]['children'][0]['text'] = 'def';
    $tree[2]['children'][0]['children'][0]['id'] = 'france-1';
    $tree[2]['children'][0]['children'][0]['text'] = 'France';
    $tree[2]['children'][0]['children'][1]['id'] = 'germany-1';
    $tree[2]['children'][0]['children'][1]['text'] = 'Germany';

    $tree[3]['id'] = 'j1_5';
    $tree[3]['text'] = 'zzz';

我的问题是我不了解如何遍历数组的'children'元素-因为深度在每个父节点之间都不同。 如果深度只有1级,我可以使用1个foreach语句,然后检查[n]['children'] ,然后遍历其中的所有项。

为了使事情更复杂,我使用CakePHP 2.x Tree行为保存数据。 这要求我在保存子元素时指定父ID,这意味着我需要按顺序遍历数组。 例如,如果我要保存“法国”(在“ abc”>“ def”下),则必须这样做:

$data['Tree']['parent_id'] = 'j1_2'; // ID of 'def'
$data['Tree']['name'] = 'France';
$this->Tree->save($data);

任何人都可以建议如何遍历此数据而无需嵌套多个foreach语句吗? 我已经读过有没有办法在不知道多维数组深度的情况下遍历多维数组? 但无法应用此方法或无法理解它是否/与我尝试执行的操作有何关系。

因此,在处理树结构时,您肯定需要一些递归和迭代器。 可能的解决方案如下所示:

/**
 * We need to extend a recursive iterator from SPL library.
 * You can reed more about it here 
 * http://php.net/manual/en/class.recursivearrayiterator.php
 */
class TreeIterator extends RecursiveArrayIterator
{
    /*
     * Originally this method returns true if current element is an array or an object
     * http://php.net/manual/en/recursivearrayiterator.haschildren.php
     * But in our case this behaviour is not suitable, so we have to redefine this method
     */
    public function hasChildren() : bool
    {
        // getting element that is used on current iteration
        $current = $this->current();

        // checking if it has any children
        return (bool) ($current['children'] ?? null);
    }

    /*
     * Originally this method returns new instance of the RecursiveArrayIterator
     * with elements that current element contains
     * http://php.net/manual/en/recursivearrayiterator.getchildren.php
     * And we have to redefine it too
     */
    public function getChildren() : self
    {
        // getting element that is used on current iteration
        $current = $this->current();

        // extracting array of child elements or assign empty array to gracefully avoid errors if it doesn't exist
        $children = $current['children'] ?? [];

        // adding to every child element id of the parent element
        array_walk($children, function (&$child) use ($current) {
            $child['parent_id'] = $current['id'];
        });

        // return new instance of our iterator
        return new self($children);
    }
}

// simply create an instance of the class with tree structure passed as an argument to constructor
$iterator = new TreeIterator($tree);

// apply the handler function for every element in an iterator
// http://php.net/manual/en/function.iterator-apply.php
// you also can use any another valid approach to iterate through it
iterator_apply($iterator, 'traverseStructure', [$iterator]);

function traverseStructure($iterator) {
    // iterate through iterator while it has any elements
    while ($iterator->valid()) {
        // get current element
        $current = $iterator->current();

        /** start: replace this block with your actual saving logic **/
        $output = sprintf('id:%10s, text:%10s', $current['id'], $current['text']);

        if (isset($current['parent_id'])) {
            $output .= sprintf(', parent_id:%10s', $current['parent_id']);
        }

        echo $output . PHP_EOL;
        /** end: replace this block with your actual saving logic **/

        // check if current element has children
        if ($iterator->hasChildren()) {
            // if it has - get children of the current element and pass it to the same function
            // we are using some recursion here
            traverseStructure($iterator->getChildren());
        }

        $iterator->next();
    }
}

该脚本的输出如下:

id:      loc1, text:    Sector
id:      loc2, text:  Location
id:   italy-1, text:     Italy, parent_id:      loc2
id:  poland-1, text:    Poland, parent_id:      loc2
id:      j1_1, text:       abc
id:      j1_2, text:       def, parent_id:      j1_1
id:  france-1, text:    France, parent_id:      j1_2
id: germany-1, text:   Germany, parent_id:      j1_2
id:      j1_5, text:       zzz

您只需要使用递归函数。

<?php

function iterate_tree($tree, $level, $parent_id) {
    foreach($tree as $index => $node) {
        foreach($node as $index => $value) {
            if($index == "children") {
                iterate_tree($value, $level + 1, $node['id']);
            } else if($index == "id") {
                $data['id'] = $node['id'];
                $data['text'] = $node['text'];
                if($parent_id != '') {
                    $data['parent_id'] = $parent_id;
                }
                echo "Level: $level <br>Data: ";
                print_r($data);
                echo "<br><br>";
            }
        }
    }
}

iterate_tree($tree, 0, '');

?>

提供以下输出:

Level: 0 
Data: Array ( [id] => loc1 [text] => Sector ) 

Level: 0 
Data: Array ( [id] => loc2 [text] => Location ) 

Level: 1 
Data: Array ( [id] => italy-1 [text] => Italy [parent_id] => loc2 ) 

Level: 1 
Data: Array ( [id] => poland-1 [text] => Poland [parent_id] => loc2 ) 

Level: 0 
Data: Array ( [id] => j1_1 [text] => abc ) 

Level: 1 
Data: Array ( [id] => j1_2 [text] => def [parent_id] => j1_1 ) 

Level: 2 
Data: Array ( [id] => france-1 [text] => France [parent_id] => j1_2 ) 

Level: 2 
Data: Array ( [id] => germany-1 [text] => Germany [parent_id] => j1_2 ) 

Level: 0 
Data: Array ( [id] => j1_5 [text] => zzz ) 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM