PHP构建树,但数组中有多个父级

PHP构建树,但数组中有多个父级,php,arrays,multidimensional-array,Php,Arrays,Multidimensional Array,我为寻找答案而痛苦了好几天,试图自己解决这个问题,但我做不到 我有一个PHP数组中的数据,其中的键parent\u id作为数组。我发现了如何构建一棵树,但前提是它只有一个父节点!但在我的例子中,它有多个父对象,并且必须嵌套在每个父对象的下面 以下是一个例子: 家长 数组( 'id'=>1, 'name'=>'Parent 1', 'parent_id'=>array() ); 数组( 'id'=>2, 'name'=>'Parent 2', 'parent_id'=>array() ); 儿童

我为寻找答案而痛苦了好几天,试图自己解决这个问题,但我做不到

我有一个PHP数组中的数据,其中的键
parent\u id
作为数组。我发现了如何构建一棵树,但前提是它只有一个父节点!但在我的例子中,它有多个父对象,并且必须嵌套在每个父对象的下面

以下是一个例子:

家长

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

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

儿童

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

我想把这棵树建成这样:

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

你能推荐一个可以帮助我的功能吗。提前谢谢

已编辑()

@杰夫·兰伯特是对的。我所做的是在元素之间循环,如果有父元素,我将其ID添加到新创建的键children。。这样我就可以随时取回它

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
}

这就是它正常工作的方式

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

使用数组中的
函数的解决方案:

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

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

实际上,您正在将每个节点的“子节点”存储在该节点索引中,而您不应该这样做,因为每个节点将与它连接到的其他节点重复相同的次数。每个节点都应该在结构的顶层表示,并包含对它们碰巧连接到的其他节点的引用,在您的
'parent\u 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),
)
然后就可以很容易地找到并实现您可能需要的任何类型的遍历算法。

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

我已经拼凑了一个与我最初的方法几乎相同的方法,除了它使用引用而不是按值赋值之外。代码如下所示:

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;
}
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;
}
此处需要双重引用,因为如果未使用
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
是两个独立的数组,或者这两个记录都在一个大数组中,这其实并不重要

因此,如果父级和子级位于单独的数组中,函数将如下所示:

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

您需要提供当前用于生成此树的代码。我以前使用过此代码,但它仅适用于单亲,而不是多亲。为什么要通过引用传递
$list
?这是毫无意义的,而且在大多数情况下,由于PHP的“写时拷贝”机制,它实际上速度较慢(除非写入,否则不会复制该值。在这种情况下,您只能从
$list
数组中读取,因此永远不会创建副本。您所做的只是增加引用计数。另外:请使用类型提示,并仔细查看OP的给定结构。我尝试了它,但仅适用于单亲!!但我有一个多父母的数组,因此每个父母下都应该列出孩子。这个同样适用@Elias!但是你能告诉我在哪里使用循环来深入吗?@KaderBouyakoub:“深入”是什么意思?您提供给我们的结构可以用这种方法处理,我看不到更多的层次。我的意思是多维数组,父级有子级,甚至子级也可能有子级。非常嵌套。(对不起我的英语)有时间我会看一看。使用递归很容易做到这一点,并首先处理有子元素的元素。这是最好且简单的解决方案!我编辑了我的问题并放置了我使用的函数。你能添加一些解释来描述这段代码如何回答这个问题吗?