2015-10-02 3 views
0

Я пытаюсь построить сплющенный массив, который сохраняет метаданные из довольно сложного массива, исходящего из представления в моем проекте CodeIgniter. Эти метаданные - это такие вещи, как идентификатор, глубина и родительский узел.PHP: создание списка смежности через рекурсивную итерацию

Данные получены из библиотеки JavaScript запросов, которая позволяет пользователю создавать правила, которые будут использоваться в бизнес-логике. Мне нужно сохранить эти данные, и модель, с которой я столкнулся, чтобы представить древовидный характер этих правил, - это список смежности.

Это то, что у меня есть, и оно действительно работает в большинстве случаев, но оно уродливое, оно изготовлено из жвачки с пузырьками и клейкой лентой, а «большинство» случаев - это не «все» случаи. После прочтения документов SPL я подозреваю, что RecursiveIteratorIterator может быть более подходящим для проблемы.

Извините за длинную ветку, но я уверен, что мой подход отстой. Любой совет?

Здесь вход (например, места я предпочел бы не быть), образец изображение, показывающее его в действии тоже:

Sample of the Query Builder

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

ответ

1

Благодаря Rocket HAZMAT для оказания помощи.

EDIT: Похоже, я пропустил какой-то код там, извинения.

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 для передачи в скрипт. К сожалению, при уровнях гнездования все вещи вышли из-под контроля при сохранении.

Вот один из примеров настроек, которые приведут к перемешиванию. В дни перед значением отображается ожидаемая подгруппа.

depicting a group that breaks the adjacency list

Возможно, это было возможно зафиксировать в рекурсивном классе итератора, но 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; 
      } 
     } 
Смежные вопросы