PHP:通过递归迭代构建邻接表
PHP: Building an Adjacency List through Recursive Iteration
我正在尝试构建一个扁平化数组,以保留来自我的 CodeIgniter 项目中一个视图的非常棘手的数组的元数据。该元数据类似于标识符、深度和父节点。
数据来自查询生成器 JavaScript 库,该库允许用户生成将在业务逻辑中使用的规则。我需要保留这些数据,而我用来表示这些规则的树状性质的模型是一个邻接列表。
这是我的,它适用于大多数情况,但它很丑,它是用泡泡糖和胶带制成的,'most' 个案例不是 'all' 个案例。阅读 SPL 文档后,我怀疑 RecursiveIteratorIterator 可能更适合这个问题。
抱歉啰嗦 post,但我很确定我的方法很糟糕。有什么建议吗?
这是输入(例如,我不想去的地方),示例图片也展示了它的实际效果:
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 的子组具有规则(Year = 2014 OR Year = 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;
}
感谢 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);
}
}
之后像这样调用和清理:(最后有点懒,改装成 CodeIgniter 运行 PHP 5.3)
$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 对象。不幸的是,在重新保存时,嵌套级别的事情变得一发不可收拾。
这是一个会导致混淆的设置示例。值之前的天数显示预期的子组。
可能可以在递归迭代器 class 中修复,但 Rocket Hazmat 建议让 class 非常简单。我在清理期间实施了修复:
$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;
}
}
我正在尝试构建一个扁平化数组,以保留来自我的 CodeIgniter 项目中一个视图的非常棘手的数组的元数据。该元数据类似于标识符、深度和父节点。
数据来自查询生成器 JavaScript 库,该库允许用户生成将在业务逻辑中使用的规则。我需要保留这些数据,而我用来表示这些规则的树状性质的模型是一个邻接列表。
这是我的,它适用于大多数情况,但它很丑,它是用泡泡糖和胶带制成的,'most' 个案例不是 'all' 个案例。阅读 SPL 文档后,我怀疑 RecursiveIteratorIterator 可能更适合这个问题。
抱歉啰嗦 post,但我很确定我的方法很糟糕。有什么建议吗?
这是输入(例如,我不想去的地方),示例图片也展示了它的实际效果:
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 的子组具有规则(Year = 2014 OR Year = 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;
}
感谢 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);
}
}
之后像这样调用和清理:(最后有点懒,改装成 CodeIgniter 运行 PHP 5.3)
$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 对象。不幸的是,在重新保存时,嵌套级别的事情变得一发不可收拾。
这是一个会导致混淆的设置示例。值之前的天数显示预期的子组。
可能可以在递归迭代器 class 中修复,但 Rocket Hazmat 建议让 class 非常简单。我在清理期间实施了修复:
$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;
}
}