简体   繁体   English

PHP:通过递归迭代构建邻接表

[英]PHP: Building an Adjacency List through Recursive Iteration

I'm trying to build a flattened array that preserves metadata from a pretty tricky array coming from a view in my CodeIgniter project. 我正在尝试构建一个扁平化的数组,该数组可以保留来自CodeIgniter项目视图中的棘手数组的元数据。 That metadata is things like an identifier, depth, and parent node. 元数据就是标识符,深度和父节点之类的东西。

The data is from a query builder JavaScript library that allows a user to generate rules that will be used in business logic. 数据来自查询构建器JavaScript库,该库允许用户生成将在业务逻辑中使用的规则。 I need to persist this data, and the model I've gone with to represent the tree-like nature of these rules is an adjacency list. 我需要保留这些数据,并且用来表示这些规则的树状性质的模型是邻接表。

Here's what I have, and it does work for most cases, but it's ugly, it's made of bubble gum and duct tape, and 'most' cases are not 'all' cases. 这就是我所拥有的,它在大多数情况下都可以使用,但是很丑陋,它是由泡泡糖和胶带制成的,而且“大多数”情况并非“全部”情况。 After reading the SPL docs, I suspect a RecursiveIteratorIterator may be more suited to the problem. 阅读了SPL文档后,我怀疑RecursiveIteratorIterator可能更适合该问题。

Sorry for the long winded post, but I'm pretty sure my approach sucks. 对冗长的帖子感到抱歉,但是我敢肯定我的方法很糟糕。 Any advice? 有什么建议吗?

Here's the input (eg, places I would rather not be), sample image showing it in action too: 这是输入(例如,我不想去的地方),示例图像也显示了它的作用:

查询生成器示例

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
                                                )
                                        )
                                )
                        )
                )
        )
)

Here is the desired output for persistence. 这是持久性所需的输出。 (See the values at the far right of each entry for the important bits of metadata). (有关元数据的重要位,请参见每个条目最右边的值)。

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 )    
)

Note: parses this correctly. 注意:正确解析。 Problems would arise if I had changed the order of subgroups 2 and 3, as the subgroup of group 3, the one that has rules (Year = 2014 OR Year = 2015) has a different nesting level and severely messes up my recursion. 如果我更改了子组2和3的顺序,则会出现问题,因为具有规则(年= 2014或年= 2015)的组3的子组具有不同的嵌套级别,并且严重干扰了我的递归。

Here is my code: 这是我的代码:

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

Thanks to Rocket Hazmat for the assist. 感谢Rocket Hazmat的协助。

EDIT: Looks like I missed some code there, apologies. 编辑:好像我错过了一些代码,抱歉。

EDIT2: There were some additional issues that arose with this approach. 编辑2:这种方法还有一些其他问题。 I show the corrections below. 我在下面显示更正。

Solution: 解:

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

Invoked and cleaned up afterwards like so: (got a little lazy around the end, retrofitting into CodeIgniter running PHP 5.3) 之后调用并清除,如下所示:(最后有点懒了,改装到运行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)));
}

The problems arose with this on the subgroup member. 与此相关的问题出现在小组成员上。 My retrieval method uses a breadth first search to create the json object to pass into the script. 我的检索方法使用广度优先搜索来创建要传递到脚本中的json对象。 Unfortunately, with the nesting levels things got out of hand when resaving. 不幸的是,有了嵌套级别,重新保存时事情就一发不可收拾。

Here is one example of settings that would result in a mix up. 这是可能导致混淆的设置示例。 The days before value shows the expected subgroup. 值之前的天数显示预期的子组。

描述一个打破邻接表的组

It may have been possible to fix in the recursive iterator class, but Rocket Hazmat suggested leaving that class very simple. 可能可以在递归迭代器类中进行修复,但Rocket Hazmat建议使该类非常简单。 I implemented a fix during the cleanup: 我在清理过程中实施了一个修复程序:

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

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

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