【问题标题】:PHP: Building an Adjacency List through Recursive IterationPHP:通过递归迭代构建邻接表
【发布时间】:2015-10-04 08:33:47
【问题描述】:

我正在尝试构建一个扁平数组,以保留来自我的 CodeIgniter 项目中一个视图的非常棘手的数组的元数据。该元数据是诸如标识符、深度和父节点之类的东西。

数据来自查询构建器 JavaScript 库,该库允许用户生成将在业务逻辑中使用的规则。我需要保留这些数据,而我用来表示这些规则的树状性质的模型是一个邻接列表。

这就是我所拥有的,它适用于大多数情况,但它很丑,它是由泡泡糖和胶带制成的,而且“大多数”情况并不是“全部”情况。阅读 SPL 文档后,我怀疑 RecursiveIteratorIterator 可能更适合该问题。

很抱歉这篇冗长的帖子,但我很确定我的方法很糟糕。有什么建议吗?

这是输入(例如,我不想去的地方),示例图片也显示了它的实际效果:

stdClass Object
(
    [condition] => OR
    [rules] => Array
        (
            [0] => stdClass Object
                (
                    [id] => Any
                    [field] => Any
                    [type] => string
                    [input] => select
                    [operator] => not equal
                    [value] => Any
                )
            [1] => stdClass Object
                (
                    [condition] => AND
                    [rules] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => Place
                                    [field] => Place
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] => France
                                )
                            [1] => stdClass Object
                                (
                                    [id] => Month
                                    [field] => Month
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] => January
                                )
                        )
                )
            [2] => stdClass Object
                (
                    [condition] => AND
                    [rules] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => Place
                                    [field] => Place
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] => Rio
                                )
                            [1] => stdClass Object
                                (
                                    [id] => Month
                                    [field] => Month
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] =>  August
                                )
                        )
                )
            [3] => stdClass Object
                (
                    [condition] => AND
                    [rules] => Array
                        (
                            [0] => stdClass Object
                                (
                                    [id] => Place
                                    [field] => Place
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] => Liberia
                                )
                            [1] => stdClass Object
                                (
                                    [id] => Month
                                    [field] => Month
                                    [type] => string
                                    [input] => select
                                    [operator] => equal
                                    [value] => July
                                )
                            [2] => stdClass Object
                                (
                                    [condition] => OR
                                    [rules] => Array
                                        (
                                            [0] => stdClass Object
                                                (
                                                    [id] => Year
                                                    [field] => Year
                                                    [type] => string
                                                    [input] => select
                                                    [operator] => equal
                                                    [value] => 2014
                                                )
                                            [1] => stdClass Object
                                                (
                                                    [id] => Year
                                                    [field] => Year
                                                    [type] => string
                                                    [input] => select
                                                    [operator] => equal
                                                    [value] => 2015
                                                )
                                        )
                                )
                        )
                )
        )
)

这是持久性所需的输出。 (有关元数据的重要位,请参阅每个条目最右侧的值)。

Array
(
    stdClass Object ( [id] => Any [field] => Any [type] => string [input] => select [operator] => not equal [value] => Any [condition] => OR [subgroup] => 0 [parent_subgroup] => )
    stdClass Object ( [id] => Place [field] => Place [type] => string [input] => select [operator] => equal [value] => France) [condition] => AND [subgroup] => 1 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Month [field] => Month [type] => string [input] => select [operator] => equal [value] => January [condition] => AND [subgroup] => 1 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Place [field] => Place [type] => string [input] => select [operator] => equal [value] => Rio [condition] => AND [subgroup] => 2 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Month [field] => Month [type] => string [input] => select [operator] => equal [value] => August[condition] => AND [subgroup] => 2 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Place [field] => Place [type] => string [input] => select [operator] => equal [value] => Liberia [condition] => AND [subgroup] => 3 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Month [field] => Month [type] => string [input] => select [operator] => equal [value] =>  July[condition] => AND [subgroup] => 3 [parent_subgroup] => 0 )
    stdClass Object ( [id] => Year [field] => Year [type] => string [input] => select [operator] => equal [value] => 2014 [condition] => OR [subgroup] => 4 [parent_subgroup] => 3 )
    stdClass Object ( [id] => Year [field] => Year [type] => string [input] => select [operator] => equal [value] => 2015 [condition] => OR [subgroup] => 4 [parent_subgroup] => 3 )    
)

注意:正确解析。如果我更改了子组 2 和 3 的顺序,就会出现问题,因为组 3 的子组具有规则(年 = 2014 或年 = 2015)具有不同的嵌套级别并严重扰乱了我的递归。

这是我的代码:

function deserialize_criteria_group($criteria, $subgroup = null) {
    $array = array();

    if ($subgroup == null) {
        $first_run = true;
        $subgroup = 0;
        $condition = $criteria->condition;
        $criteria = $criteria->rules;
    }

    foreach ($criteria as $rule) {
        if ($rule->rules) {
            $subgroup++;
            $children = $this->deserialize_criteria_group($rule->rules, $subgroup);
            foreach($children as $child) {
                if ($child->condition == null) {
                    $child->condition = $rule->condition;
                }
                if ($child->parent_subgroup == null) {
                    $child->parent_subgroup = $first_run ? 0 : $subgroup - 1;
                }
                    array_push($array, $child);
            }
        } else {
            $rule->condition = $condition;
            $rule->subgroup = $subgroup;
            $rule->parent_subgroup = null;
            array_push($array, $rule);
        }

    }

    if ($first_run) {
        //Ensure a root node exists, if not stub one out. 
        $criteria_group = json_decode(json_encode($array), true);
        $root_encountered = $criteria_group[0]['subgroup'] > 0 ? false : true;
        if (!$root_encountered) {
            $root = array(  'subgroup'          => 0, 
                            'parent_subgroup'   => null, 
                            'condition'         => $condition);
            array_unshift($criteria_group, $root); 
            array_unshift($array, $root);
        }

        //Ensure the ALM is not broken. 
        $subgroup = 0;
        foreach($criteria_group as $c) {
            if($c['subgroup'] > $subgroup + 1) {
                $msg = "Bad Order. Halting execution.";
                print $msg; 
                log_message('error', $msg); 
                log_message('debug', 'expected: ' . $subgroup . ' actual: ' . $c['subgroup']);
                log_message('debug', print_r($criteria_group, true));
                die;
            }
            $subgroup = $c['subgroup'];
        }
    }
    return $array;
}

【问题讨论】:

    标签: php recursion persistence adjacency-list


    【解决方案1】:

    感谢 Rocket Hazmat 的协助。

    编辑:看起来我错过了那里的一些代码,抱歉。

    EDIT2:这种方法产生了一些额外的问题。我在下面显示更正。

    解决办法:

    <?php
    class CriteriaIterator implements RecursiveIterator{
        private $data, $counter, $condition, $subgroup, $parent_subgroup;
    
        public function __construct($criteriaGroup, $condition, $parent_subgroup=null){
                $this->condition = $condition;
                $this->subgroup = $GLOBALS['highest_subgroup'];
                $this->parent_subgroup = $parent_subgroup;
                $this->data = is_array($criteriaGroup) ? $criteriaGroup : array($criteriaGroup);
        }
    
        public function current(){
                $row = $this->data[$this->counter];
                if ($row->id) {
                        return (object) array(
                                'id' => $row->id,
                                'field' => $row->id,
                                'operator' => $row->operator,
                                'value' => $row->value,
                                'condition'=> $this->condition,
                                'subgroup' => $GLOBALS['highest_subgroup'],
                                'parent_subgroup' => $this->parent_subgroup
                        );
                }
        }
    
        public function key(){
                return $this->counter;
        }
    
        public function next(){
                $this->counter++;
        }
    
        public function rewind(){
                $this->counter = 0;
        }
    
        public function valid(){
            return $this->counter < count($this->data);
        }
    
        public function hasChildren(){
            $row = $this->data[$this->counter];
            return isset($row->rules);
        }
    
        public function getChildren(){    
            $GLOBALS['highest_subgroup']++;
            $row = $this->data[$this->counter];
            return new self($row->rules, $row->condition, $this->subgroup);
        }
    }
    

    之后像这样调用和清理:(最后有点懒惰,改装到运行 PHP 5.3 的 CodeIgniter)

    $records = new RecursiveIteratorIterator(
        new CriteriaIterator($a['criteria_group'], $a['criteria_group']->condition),
        RecursiveIteratorIterator::SELF_FIRST);
    
    $criteria = array();
    $parent_encountered = false;
    
    // cleanup
    foreach($records as $row) {
        if($row != null) {
            $row->parent_subgroup = $row->parent_subgroup == - 1 ? null : $row->parent_subgroup;
            if($row->parent_subgroup === null) {
                $parent_encountered = true;
            }
            array_push($criteria, $row);
        }
    }
    
    if(!$parent_encountered) {
        $row = array(
            'subgroup' => 0,
            'parent_subgroup' => null,
            'condition' => $a['criteria_group']->condition
        );
        array_unshift($criteria, json_decode(json_encode($row)));
    }
    

    问题出现在子组成员上。我的检索方法使用广度优先搜索来创建要传递给脚本的 json 对象。不幸的是,在重新保存时,由于嵌套级别,事情变得一发不可收拾。

    下面是一个会导致混淆的设置示例。值之前的天数显示预期的子组。

    可能已经可以在递归迭代器类中进行修复,但 Rocket Hazmat 建议让该类非常简单。我在清理期间实施了修复:

            $records = new RecursiveIteratorIterator(
                    new CriteriaIterator($a['criteria_group'], $a['criteria_group']->condition), 
                    RecursiveIteratorIterator::SELF_FIRST);
    
            $criteria = array();
            $root_encountered = false;
    
            // cleanup
            foreach($records as $row) {
                if($row != null) {
                    if($row->parent_subgroup == - 1) {
                        $row->parent_subgroup = null;
                        $row->subgroup = 0;
                    } 
                    if($row->parent_subgroup === null) {
                        $root_encountered = true;
                    }
                    array_push($criteria, $row);
                }
            }
    
            if(!$root_encountered) {
                $row = (object) array(
                        'subgroup' => 0,
                        'parent_subgroup' => null,
                        'condition' => $a['criteria_group']->condition 
                );
                array_unshift($criteria, $row);
            }
    
            //strategy: keep a record keyed by subgroups of where they are rooted. 
            //if an entry exists for a previous subgroup but the parent subgroup conflicts
            //use the subgroup of the LAST subgroup rooted there. 
            //else update array
    
            $adjacency = array(0 => null); //parent
            foreach($criteria as $row) {
                if (isset($adjacency[$row->subgroup]) && $adjacency[$row->subgroup] != $row->parent_subgroup) {
                    $preserved = array_reverse($adjacency, true); //need LAST subgroup rooted there
                    foreach($preserved as $key=>$val) {
                        if ($val == $row->parent_subgroup) {
                            $row->subgroup = $key;
                            break;
                        }
                    }
                } else {
                    $adjacency[$row->subgroup] = $row->parent_subgroup;
                }
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-01
      相关资源
      最近更新 更多