【问题标题】:PHP Flat object to hierarchical objectPHP平面对象到分层对象
【发布时间】:2021-03-29 12:00:42
【问题描述】:

已经有一些关于这个主题的问题,但它们通常与将分层对象转换为平面对象有关,这对我来说不是问题。但是,我目前在从平面对象创建分层对象方面完全被难住了。

假设如下简化的数据库表

id | parent 
 1 |   0
 2 |   1 
 3 |   1
 4 |   3
 5 |   4

数据库查询后,返回以下对象: $查询结果:

[{id:1, parent:0}, 
 {id:2, parent:1}, 
 {id:3, parent:1}, 
 {id:4, parent:3},
 {id:5, parent:4}]

我试图通过递归方法完成的是创建一个与以下内容相同的对象:

[{
   id:1,
   children:[
      {id:2},
      {id:3,
          children:[
              {id:4, 
               children:[
                  {id:5}
               ]
              }
          ]
      }  
   ]
}] 

我有一些开始,但我的大脑无法再跟上...... 有没有人对返回所需对象的递归函数有任何建议? 下面是我目前拥有的脏代码(想想涂鸦)。它正常运行,直到该函数被递归调用并开始复制条目。

function CreateObject($object, $new = array()){

    for($i = 0; $i < count($object); $i++){
        $found = false;
        foreach($object[$i] as $key => $value){
            if ($key == 'parent'){
                if ($value == 0){
                    array_push($new, $object[$i]);
                } else {
                    //push into parent
                    for($j = 0; $j < count($new); $j++){
                        foreach($new[$j] as $k => $v){
                            if ($k == 'id' && $v == $value){
                                $found = true;
                               
                                if (property_exists($new[$j], 'children')){
                                    array_push($new[$j]->children, $object[$i]);
                                } else {
                                    $new[$j]->children = array();
                                    array_push($new[$j]->children, $object[$i]);
                                }

                            }
                        }
                        
                        if (!$found){
                            foreach($new[$j] as $k => $v){
                                if ($k == 'children'){
                                    CreateObject($object, $v);
                                }
                            }
                           break;
                        }  
                    }
                }
            }
        }
    } 
    
    return $new;
}


$hierarchalObject = CreateObject($flatObject);

使用快速创建的对象:(这会导致查询返回完全相同的对象)

$flatObject = array(
    array('id' => 1, 'parent' => 0),
    array('id' => 2, 'parent' => 1),
    array('id' => 5, 'parent' => 2),
    array('id' => 6, 'parent' => 2),
    );

$flatObject = json_decode(json_encode($flatObject));

返回:

[
    {
        "id": 1,
        "parent": 0,
        "children": [
            {
                "id": 2,
                "parent": 1,
                "children": [
                    {
                        "id": 5,
                        "parent": 2
                    },
                    {
                        "id": 6,
                        "parent": 2
                    },
                    {      //DUPLICATES HERE
                        "id": 5,
                        "parent": 2
                    },
                    {
                        "id": 6,
                        "parent": 2
                    }
                ]
            }
        ]
    }
]

我尽量做到完整。对于我疼痛的大脑,任何帮助将不胜感激。

更新

感谢 C3iG3 的回答,以下功能非常适合我的情况。这是 C3iG3 答案的一个非常轻微修改的版本,包括所有节点的属性,如果它是空的,则不包括子属性。

<?php
        function unflatten ( $data )
        {
            /* Create new node objects */
            function create_new_node ( $o ) {
                return new class ( $o ) {
                    public $id;

                    public function __construct ( $o ) { 
                        //get all properties and assign to node
                        foreach($o as $key => $value){
                            if ($key != 'parent'){
                                $this->$key = $value;
                            }
                        }
                    }
                };
            }            
        
            $roots = array(); // store root nodes
            $nodes = array(); // store all nodes
        
            foreach ( $data as $o ) 
            {
                $node = isset( $nodes[$o->id] ) ? $nodes[$o->id] : null; // check if node was already created
        
                if ( !$node ) {
                    $node = create_new_node($o);
                }
        
                $nodes[$o->id] = $node; // keep track of node
        
                if ( $o->parent ) // node has a parent
                {
                    if ( !isset( $nodes[$o->parent] ) ) { // if parent node was not already created
                        $nodes[$o->parent] = create_new_node( $o->parent );
                    }
                    $nodes[$o->parent]->children[] = $node; // add node to parent
                } else {
                    $roots[] = $node;
                }
            }
        
            return $roots;
        }
?>

【问题讨论】:

  • 我能想到的最简单的事情是将子数组中每个元素的索引设置为 id 以避免重复。所以"children": [5 =&gt; {id: 5, parent: 2}, 6 =&gt; {id: 6, parent: 2}, ... ]。然后不要使用array_push,我认为你可以设置像$new[$j]-&gt;children[$k] = $object[$i];这样的值
  • 感谢您对 WOUNDEDStevenJones 的洞察力,它最终成为错误的方法。

标签: php object hierarchical flat


【解决方案1】:

这个问题不太适合递归解决方案。其原因主要是因为您的数据的结构。您的每个对象都跟踪其父对象,而不是其子对象。

递归函数没有明显的起点。如果您从父对象开始,您不知道要将哪些子对象绑定到父对象,因此无法划分问题(无法向下递归层次结构)。您可以递归层次结构(通过遵循父级),但由于这不会以任何方式划分问题,因此递归编写不会受益。您当然可以编写一个递归解决方案来创建节点、遍历现有层次结构并插入它们,但这对于大型层次结构会变得低效。

相反,这个问题更适合迭代解决方案。

<?php

function unflatten ( $data )
{
    /* Create new node objects */
    function create_new_node ( $id ) {
        return new class ( $id ) {
            public $id;
            public $children = array();
            
            public function __construct ( $id ) { $this->id = $id; }
        };
    }

    $roots = array(); // store root nodes
    $nodes = array(); // store all nodes

    foreach ( $data as $o ) 
    {
        $node = isset( $nodes[$o->id] ) ? $nodes[$o->id] : null; // check if node was already created

        if ( !$node ) {
            $node = create_new_node( $o->id );
        }

        $nodes[$o->id] = $node; // keep track of node

        if ( $o->parent ) // node has a parent
        {
            if ( !isset( $nodes[$o->parent] ) ) { // if parent node was not already created
                $nodes[$o->parent] = create_new_node( $o->parent );
            }
            $nodes[$o->parent]->children[] = $node; // add node to parent
        } else {
            $roots[] = $node;
        }
    }

    return $roots;
}

$queryResult = json_decode('[{"id":1, "parent":0}, {"id":2, "parent":1}, {"id":3, "parent":1}, {"id":4, "parent":3}, {"id":5, "parent":4}]' );

echo json_encode( unflatten( $queryResult ) );

这将生成以下结构:

[{
    "id": 1,
    "children": [{
        "id": 2,
        "children": []
    }, 
    {
        "id": 3,
        "children": [{
            "id": 4,
            "children": [{
                "id": 5,
                "children": []
            }]
        }]
    }]
}]

此解决方案需要对原始对象集进行一次遍历,并在恒定时间内将它们附加到其父对象。必须跟踪 nodes 数组中的所有现有节点会产生一些轻微的开销。

这个解决方案可以重写为递归,但最终会建模一个循环结构(这是一个很好的指标,表明问题更适合迭代编写)。

【讨论】:

  • 谢谢!我越来越难过......我正在使用递归函数来读取层次结构输出,效果很好,所以我认为相反的也需要递归函数。如果节点还具有其他属性,我已经稍微修改了您的代码以使其正常工作。
猜你喜欢
  • 2021-11-12
  • 2016-06-20
  • 1970-01-01
  • 1970-01-01
  • 2020-11-15
  • 2011-01-10
  • 2018-12-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多