PHP构建树但是数组中有多个父节点

时间:2016-08-04 16:52:13

标签: php arrays multidimensional-array

我几天都在寻找答案,并试图自己解决问题,但我不能。

我在PHP数组中有数据,其中键parent_id为数组。我找到了如何构建树,但只有它只有一个父亲!但在我的情况下,它有多个父母,它必须嵌套在每个父母之下。

以下是一个例子:

家长

array( 'id' => 1, 'name' => 'Parent 1', 'parent_id' => array() );

array( 'id' => 2, 'name' => 'Parent 2', 'parent_id' => array() );

儿童

array( 'id' => 3, 'name' => 'Child 1', 'parent_id' => array(1, 2) );

我想要像这样构建树:

array( 'id' => 1, 'name' => 'Parent 1', 'parent_id' => array(), 'children' => array( array( 'id' => 3, 'name' => 'Child 1', 'parent_id' => array(1, 2) ) ), ); array( 'id' => 2, 'name' => 'Parent 2', 'parent_id' => array(), 'children' => array( array( 'id' => 3, 'name' => 'Child 1', 'parent_id' => array(1, 2) ) ), );

你能否建议一个可以帮助我的工作功能。提前谢谢。

已编辑(DEMO

@Jeff Lambert 是对的。我做的是遍历元素,如果有父母,我将 ID 添加到新创建的密钥孩子 ..这样我可以随时检索它。

function build_tree(array $elements)
{
    $indexed = array();
    foreach($elements as $element)
    {
        $element = (array) $element;
        $indexed[$element['id']] = $element;
    }

    $elements = $indexed;
    unset($indexed, $element);

    foreach($elements as $id => $element)
    {
        if ( ! empty($element['parent_id']))
        {
            foreach($element['parent_id'] as $parent)
            {
                if (isset($elements[$parent]))
                {
                    $elements[$parent]['children'][] = $element['id'];
                }
            }
        }
    }

    return $elements;
}

然后我只需要创建一些函数来检索元素细节,如下所示:

function get_element($id, $return = NULL)
{
    // Check the element inside the array
    if (isset($elements[$id])
    {
        // In case I want to return a single value
        if ($return !== NULL and isset($elements[$id][$return])
        {
            return $elements[$id][$return];
        }
        return $elements[$id];
    }
    return FALSE; // Or NULL, as you wish
}

5 个答案:

答案 0 :(得分:0)

这就是它将如何正常工作。

 $arr = array(
      array('id'=>100, 'parentid'=>0, 'name'=>'a'),
      array('id'=>101, 'parentid'=>100, 'name'=>'a'),
      array('id'=>102, 'parentid'=>101, 'name'=>'a'),
      array('id'=>103, 'parentid'=>101, 'name'=>'a'),
    );

    $new = array();
    foreach ($arr as $a){
        $new[$a['parentid']][] = $a;
    }
    $tree = createTree($new, array($arr[0]));
    print_r($tree);

    function createTree(&$list, $parent){
        $tree = array();
        foreach ($parent as $k=>$l){
            if(isset($list[$l['id']])){
                $l['children'] = createTree($list, $list[$l['id']]);
            }
            $tree[] = $l;
        } 
        return $tree;
    }

答案 1 :(得分:0)

使用in_array函数的解决方案:

// $parents and $children are arrays of 'parents' and 'children' items respectively
$tree = [];
foreach ($parents as $p) {
    $treeItem = $p + ['children' => []];
    foreach ($children as $c) {
        if (in_array($p['id'], $c['parent_id']))
            $treeItem['children'][] = $c;
    }
    $tree[] = $treeItem;
}

print_r($tree);

DEMO link

答案 2 :(得分:0)

每个节点可以拥有多个父节点的树不是树,而是graph。表示图表的一种方法是通过adjacency list

实际上,您将每个节点的“子节点”存储在节点索引中,您不应该因为每个节点的重复次数与其他节点的重复次数相同连接到。每个节点都应该在结构的顶层表示,并包含对它们恰好连接到的其他节点的引用,在您的情况下是'parent_id'索引。我将分离出节点的实际定义,并在单独的结构中声明每个节点连接的其他节点。

用于定义节点的这些内容:

array(
    0 => array(
        'id'        => 1,
        'name'      => 'Parent 1',
    ),
    1 => array(
        'id'        => 2,
        'name'      => 'Parent 2',
    ),
    2 => array(
        'id'        => 3,
        'name'      => 'Child 1',
    ),
)

然后是一个单独的数组,用于定义节点之间的连接:

array(
    // These indices match the node indices above, and the values are the list of 
    // node indices each node has a connection to.
    0 => array(2),
    1 => array(2),
    2 => array(0, 1),
)

然后,应该很容易找到并实现您可能需要的任何类型的遍历算法。

答案 3 :(得分:0)

更新

如果您希望/需要嵌套节点(例如,父节点可以有一个或多个子节点,同时又是另一个父节点的子节点),那么最简单的方法是为节点分配引用。

我已经将quick demo与我原来的方法几乎完全相同,除了它使用引用而不是按值分配。代码如下所示:

function buildTree(array $data)
{
    $data = array_column($data, null, 'id');
    //reference to each node in loop
    foreach ($data as &$node) {
        if (!$node['parent_id']) {
            //record has no parents - null or empty array
            continue; //skip
        }
        foreach ($node['parent_id'] as $id) {
            if (!isset($data[$id])) { // make sure parent exists
                throw new \RuntimeException(
                    sprintf(
                        'Child id %d is orphaned, no parent %d found',
                        $node['id'], $id
                    )
                );
            }
            if (!isset($data[$id]['children']) {
                $data[$id]['children'] = array();
            }
            $data[$id]['children'][] = &$node; //assign a reference to the child node
        }
    }
    return $data;
}

此处需要双引用,因为如果您未使用foreach ($data as &$node),则$node变量将是原始节点的副本。分配对副本的引用对你没有任何好处。事实上,它会产生错误的结果。

同样,如果您没有从循环中为&$node分配引用,则不会获得整个树中子节点的完整列表。
这不是最容易解释的事情,但最终结果不言自明:使用这里的引用允许您在单个函数调用中完整地构建树。​​

这就是我的所作所为。首先,我使用id作为数组键,因此我可以更轻松地找到每个孩子的父母:

$parents = array_column($parents, null, 'id');

如果你使用旧版本的PHP,并且无法升级,这相当于写作:

$indexed = array();
foreach ($parents as $parent) {
    $indexed[$parent['id']] = $parent;
}
$parents = $indexed;

现在迭代孩子们,并将他们分配给他们的父母:

foreach ($children as $child) {
    foreach ($child['parent_id'] as $id) {
        if (!isset($parents[$id]['children']) {
            $parents[$id]['children'] = array();//ensure the children key exists
        }
        $parents[$id]['children'][] = $child;//append child to parent
    }
}

如果$parents$children是2个独立的数组,或者两个记录都在这里的一个大数组中,那真的无关紧要。

因此,如果父项和子项位于不同的数组中,则函数将如下所示:

function buildTree(array $parents, array $children)
{
    $parents = array_column($parents, null, 'id');
    foreach ($children as $child) {
        foreach ($child['parent_id'] as $id) {
            if (!isset($parents[$id])) { // make sure parent exists
                throw new \RuntimeException(
                    sprintf(
                        'Child id %d is orphaned, no parent %d found',
                        $child['id'], $id
                    )
                );
            }
            if (!isset($parents[$id]['children']) {
                $parents[$id]['children'] = array();
            }
            $parents[$id]['children'][] = $child;
        }
    }
    return $parents;
}

如果所有数据都在一个数组中,那么该函数看起来几乎相同:

function buildTree(array $data)
{
    $data = array_column($data, null, 'id');
    foreach ($data as $node) {
        if (!$node['parent_id']) {
            //record has no parents - null or empty array
            continue; //skip
        }
        foreach ($node['parent_id'] as $id) {
            if (!isset($data[$id])) { // make sure parent exists
                throw new \RuntimeException(
                    sprintf(
                        'Child id %d is orphaned, no parent %d found',
                        $node['id'], $id
                    )
                );
            }
            if (!isset($data[$id]['children']) {
                $data[$id]['children'] = array();
            }
            $data[$id]['children'][] = $node;
        }
    }
    return $data;
}

答案 4 :(得分:0)

$data = [
        ['id' => 1, 'parent' => []],
        ['id' => 2, 'parent' => [1]],
        ['id' => 3, 'parent' => [2,4]],
        ['id' => 4, 'parent' => []]
    ];

$result = [];

foreach ($data as $item) {
    if(!count($item['parent'])) {
        makeTree($result, $item, $data);
    }
}

print_r($result);

function makeTree(&$result, $item, $data) {
    $result['children'][$item['id']]['data'] = $item;
    if(haveChildren($item['id'], $data)) {
        foreach(children($item['id'], $data) as $child) {
            makeTree($result['children'][$item['id']], $child, $data);
        }
    }
}

function children($id, $data){
    $result = [];
    foreach($data as $item) {
        if(in_array($id, $item['parent'])) {
            $result[] = $item;
        }
    }
    return $result;
}

function haveChildren($id, $data) {
    foreach($data as $item) {
        if(in_array($id, $item['parent'])) {
            return true;
        }
    }
}