Advertisement
rfv123

Q35484259 - 'TreeWalk' Paths in Array Class

Feb 22nd, 2016
214
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 11.20 KB | None | 0 0
  1. <?php
  2. /**
  3.  *  Interface can be found at:
  4.  */
  5.  
  6.      
  7. /*
  8.  * Tree Node:
  9.  *
  10.  * Array(
  11.  *        mixed => mixed
  12.  *      );
  13.  *
  14.  */
  15.  
  16. /*
  17.  * NodeProcessor:
  18.  *     It is a 'callable' that accepts one parameter
  19.          o An instance of the TreeWalk class
  20.          
  21.          There are available method that will be useful
  22.  */
  23.  
  24. /**
  25.  *  Traverse the tree of `nodes` by walking the paths
  26.  *
  27.  *    o Keep a `current path` from Root to the `current node` as an array of `node data`.
  28.  *    o Call the 'nodeProcessor for each node'.
  29.  */
  30.  
  31. class TreeWalk implements ITreeWalk {
  32.  
  33.     /**
  34.      * The 'current' tree
  35.      *
  36.      * @var array $tree
  37.      */
  38.     private $tree = array();
  39.  
  40.     /**
  41.     * Terminate the processing at any node
  42.     *
  43.     * @var boolean $endTreeWalk
  44.     */
  45.     public $endTreeWalk = false;
  46.  
  47.     /**
  48.     * A reference to the current node so it can be updated
  49.     *
  50.     * @var mixed $currentNode
  51.     */
  52.     public $currentNode = null;
  53.  
  54.     /**
  55.     * The key of the currentData - readonly
  56.     *
  57.     * @var mixed $currentKey
  58.     */
  59.     public $currentKey = null;
  60.  
  61.     /**
  62.      * The 'current' stack of nodes in this path
  63.      *
  64.      * This is a 'stack'. The 'path' is all the entries combined.
  65.      *
  66.      * @var array $currentPath
  67.      */
  68.     public $currentPath = array();
  69.  
  70.  
  71.     /**
  72.      * Available data to the nodeProcesor
  73.      *
  74.      * @var mixed $processorData
  75.      */
  76.     public $processorData = array();
  77.  
  78.     /**
  79.      * The Output
  80.      *
  81.      * @var array $processorResults
  82.      */
  83.     public $processorResults = array();
  84.  
  85.  
  86.     /**
  87.      * The 'callable' to be run for nodes
  88.      *
  89.      * @var callable $nodeProcessor
  90.      */
  91.     protected $nodeProcessor = null;
  92.  
  93.  
  94.     /* internal data */
  95.    
  96.     private $depth      = 0;
  97.  
  98.     /**
  99.      * Build the class but do not run it...
  100.      *
  101.      * Provides a default NodeProcessor if you don't provide one.
  102.      *    o The default processor builds string paths that look like  array paths
  103.      *
  104.      * @param array $tree
  105.      * @param callable $processNode        - optional
  106.      * @param processorData                - optional
  107.      */
  108.     public function __construct(array &$tree,
  109.                                 /* callable */ $processNode = null,
  110.                                 /* mixed    */ $processorData = null)
  111.     {
  112.         if (empty($tree)) {
  113.             throw new \Exception('Empty tree is not very interesting.', 500);    
  114.         }
  115.        
  116.         $this->tree =& $tree;
  117.         $this->nodeProcessor = $processNode;
  118.  
  119.         $this->processorData = $processorData;
  120.  
  121.         // provide a default processor
  122.         if (!$this->nodeProcessor) {
  123.             $this->nodeProcessor = $this->defaultNodeProcessor();
  124.         }
  125.     }
  126.  
  127.     /**
  128.      * This routine makes this class rather powerful as you can use any callable.
  129.      *
  130.      * @param type $nodeProcessor
  131.      */
  132.     public function setNodeProcessor(/* callable */ $nodeProcessor)
  133.     {
  134.         $this->nodeProcessor = $nodeProcessor;
  135.     }
  136.    
  137.     public function setProcessorData($data)
  138.     {
  139.         $this->processorData = $data;    
  140.     }
  141.  
  142.     /**
  143.      * Return a list of results were generated by the 'nodeProcessor'
  144.      * @return array
  145.      */
  146.     public function results()
  147.     {
  148.         return $this->processorResults;
  149.     }
  150.  
  151.     /**
  152.      * Process all the  nodes.
  153.      *
  154.      * @return void
  155.      */
  156.     public function treeWalk($processorData = null)
  157.     {
  158.         if (!is_null($processorData)) {
  159.             $this->processorData = $processData;
  160.         }
  161.         $this->processorResults = array();
  162.         $this->endTreeWalk = false;
  163.  
  164.         array_push($this->currentPath, array('key' => 'root', 'data' => $this->tree));
  165.         $this->walk('root', $this->tree);
  166.         array_pop($this->currentPath);
  167.         return;
  168.     }
  169.  
  170.  
  171.     /**
  172.      * The routine that processes one node and recurses as required
  173.      *
  174.      * @param array $node
  175.      * @return void This is all side-effects
  176.      */
  177.     protected function walk($nodeKey, &$nodeData)
  178.     {
  179.         if ($this->endTreeWalk) { return; } // nothing to do but let the tree unwind
  180.            
  181.         // now process all the children...
  182.         if (is_array($nodeData) && !empty($nodeData)) { // process the array
  183.            
  184.             foreach ($nodeData as $key => &$nextData) {
  185.                 if ($this->endTreeWalk) { break; } // nothing to do but let the tree unwind
  186.  
  187.                 $this->currentNode =& $nextData;
  188.                 $this->currentKey  =  $key;
  189.  
  190.  
  191.                                                        
  192.                 if (is_array($nextData) && !empty($nextData)) { // nest one level
  193.                
  194.                     $this->depth++;
  195.                     array_push($this->currentPath, array('key' => $key, 'data' => $nextData));
  196.  
  197.                     $this->callNodeProcessor(); // process the node
  198.                     if ($this->endTreeWalk) { break; } // nothing to do but let the tree unwind
  199.                    
  200.                     $this->walk($key, $nextData); // recurse
  201.                     array_pop($this->currentPath);
  202.                     $this->depth--;
  203.                     continue; // all done with this entry
  204.                 }    
  205.                
  206.                 array_push($this->currentPath, array('key' => $key, 'data' => $nextData));
  207.                 $this->callNodeProcessor(); // process the node
  208.                 array_pop($this->currentPath); // lose  node from top of stack
  209.             }
  210.            
  211.             unset($nextData);
  212.             unset($this->currentNode); // remove all reference so if will be garbage collected
  213.         }
  214.         else { // node data will not be an array with values
  215.             if ($this->endTreeWalk) { return; } // nothing to do but let the tree unwind
  216.            
  217.             $this->currentNode =& $nodeData;
  218.             $this->currentKey  =  $nodeKey;
  219.            
  220.             array_push($this->currentPath, array('key' => $nodeKey, 'data' => $nodeData));            
  221.             $this->callNodeProcessor();
  222.             array_pop($this->currentPath); // lose node from top of stack
  223.            
  224.             unset($this->currentNode); // remove all reference so it will be garbage collected
  225.         }
  226.        
  227.         return; // end of children
  228.     }
  229.  
  230.     /**
  231.      * The `callable` to be called.
  232.      *
  233.      * It must accept one parameter.
  234.      *
  235.      * It can be set after the 'class instance' is created.
  236.      *
  237.      * @param object instance of this class
  238.      *
  239.      * @return mixed if not empty will be added added to the result
  240.      */
  241.     protected function callNodeProcessor()
  242.     {
  243.         $result = call_user_func($this->nodeProcessor,
  244.                                         $this);
  245.        
  246.         if (!empty($result)) { // add to the result array
  247.             $this->processorResults[] = $result;
  248.         }
  249.         return;        
  250.     }
  251.  
  252.     /**
  253.      * If you don't provide a callable to generate paths then this will be used.
  254.      *
  255.      * It generates a list string paths to all the leaf nodes.
  256.      *
  257.      * It deliberately uses the current node data - it is part of testing really
  258.      *
  259.      * @return string
  260.      */
  261.     public function defaultNodeProcessor()
  262.     {
  263.         $dnp = function (TreeWalk $tree) {
  264.        
  265.                   if (is_array($tree->currentNode) && !empty($tree->currentNode)) {
  266.                       return ''; // not a leaf node
  267.                   }
  268.  
  269.                   return $tree->showCurrentPath();
  270.                };
  271.              
  272.         return $dnp;
  273.     }
  274.  
  275.     /**
  276.     * show the current path as text  -- it is a little clumsy - hopefully easy to debug :)
  277.     *
  278.     * What if you don't use `foreach` - is it easier to understand? It is for me...
  279.     *
  280.     * Path stucture:
  281.     *   1) root node                                       (required)
  282.     *   2) an iteration of intermediate nodes              (optional) - makes sense
  283.     *   3) end data node                                   (optional) !
  284.     *        - what? yes - what if path is just the root ;-/
  285.     *
  286.     * @return string
  287.     */    
  288.     public function showCurrentPath()
  289.     {
  290.         $textPath = ''; // output text path
  291.         $curIdx = 0;    // which entry we are playing with
  292.         $path = $this->currentPath; // simplify the code in here - is readonly so is cheap
  293.  
  294.  
  295.         // I need to know which is the last node...
  296.         $lastIndex = count($path) - 1;
  297.        
  298.         $textPath = $path[$curIdx]['key']; // build the output starting at the root
  299.         $curIdx++;                // start of the path proper
  300.              
  301.         // use $curIdx - process 'path' nodes but not the last one as that has data
  302.         //               that needs to be treated differently  
  303.         for (/* already set */; $curIdx < $lastIndex; $curIdx++ ) {
  304.             $textPath .= '['. $path[$curIdx]['key'] . ']';
  305.         }                                        
  306.        
  307.         // the last node has various conditions... does it exist?
  308.         if ($curIdx === $lastIndex) { // ok - process the last node
  309.  
  310.             $textPath .= '['. $path[$curIdx]['key'] . ']'; // key to the data in this node    
  311.            
  312.             $data = $path[$curIdx]['data']; // simplify `data` processing code - no other reason
  313.        
  314.             if (is_array($data)) {
  315.                 if (!empty($data)) {
  316.                         $textPath .= ' -'. 'array' . '- ';
  317.                 }
  318.                 else {
  319.                     $textPath .= ' -'. 'empty array' . '- ';
  320.                 }
  321.             }
  322.             else {
  323.                 $textPath .= ' -'. $data . '- ';  
  324.             }  
  325.         }
  326.         return rtrim($textPath);
  327.     }
  328.  
  329.   /* ----------------------------------------------------------------------------------------
  330.    * Path routines - worth refactoring to separate class? maybe - not now...
  331.    */    
  332.    
  333.    /**
  334.     * How many items on the path??
  335.     *  
  336.     * @return integer
  337.     */
  338.    public function pathDepth()
  339.     {
  340.         return count($this->currentPath);
  341.     }
  342.  
  343.    /**
  344.     * The key of the item on the path
  345.     *
  346.     * return top of stack as default
  347.     *
  348.     * @param integer $idx
  349.     *
  350.     * @return mixed
  351.     */
  352.     public function pathKey($idx = -1)
  353.     {
  354.         $idx = max(min($this->pathDepth() - 1, $idx), 0); // in range
  355.  
  356.         if (isset($this->currentPath[$idx])) {
  357.             return $this->currentPath[$idx]['key'];
  358.         }
  359.     }
  360.  
  361.    /**
  362.     * The data available at that entry in the path
  363.     *
  364.     * return top of stack as default
  365.     *    
  366.     * @param integer $idx
  367.     *
  368.     * @return mixed
  369.     */
  370.     public function pathData($idx = -1)
  371.     {
  372.         $idx = max(min($this->pathDepth() - 1, $idx), 0); // in range
  373.  
  374.         if (isset($this->currentPath[$idx])) {
  375.             return $this->currentPath[$idx]['data'];
  376.         }
  377.         return null;
  378.     }
  379.  
  380.    /**
  381.     * is the path entry set?
  382.     *
  383.     * @param integer $idx
  384.     *
  385.     * @return boolean
  386.     */
  387.     public function issetPath($idx = -1)
  388.     {
  389.         $idx = max(min($this->pathDepth() - 1, $idx), 0); // in range
  390.  
  391.         return isset($this->currentPath[$idx]);
  392.     }    
  393. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement