具有任意分支因子的树节点和迭代器

问题描述 投票:0回答:1

我正在尝试编写具有任意分支因子和节点级别索引和值的树。

什么是好的代码?

我尝试查看 PHP 中可用的内容,但想编写自己的代码。

有什么想法吗?

我希望迭代器能够同时进行深度和呼吸。

谢谢。

php class tree iterator arbitrary-values
1个回答
0
投票

我创建的以下代码效果很好。

我希望其他人可以使用它并发现它有用。

<?php
/**
 * Tree nodes including nameless non-leaf node used to construct nonempty nontrivial tree root node,
 * named non-leaf node with children, and leaf node with name and value.
 *
 * @author Neil Glen Zanella <[email protected]>
 * @copyright 2004 Neil Glen Zanella
 * @version v9 (modified method for retrieving path to node with condition from node)
 */

class TreeNodeFactory {

  public static function createStubNode() {

    return new TreeNode(null, null, array());

  }

  public static function createNamelessNonleafNode($children) {

    $treeNode = new TreeNode(null, null, $children);

    TreeNode::checkTreeNodeHasChildren($treeNode);

    return $treeNode;

  }

  public static function createNonLeafNode($name, $children) {

    $treeNode = new TreeNode($name, null, $children);

    TreeNode::checkTreeNodeHasChildren($treeNode);

    return $treeNode;

  }

  public static function createLeafNode($name, $value) {

    $treeNode = new TreeNode($name, $value, array());

    TreeNode::checkTreeNodeHasNoChildren($treeNode);

    return $treeNode;

  }

}

class TreeNode {

  protected $name;

  protected $value;

  protected $children;

  protected $customData;

  public function __construct($name, $value, $children) {

    $this->setName($name);

    $this->setValue($value);

    $this->setChildren($children);

    $this->setCustomData(null);

  }

  public static function checkTreeNode($treeNode) {

    // check whether tree node is not null and is a class instance

    if (!is_a($treeNode, __CLASS__)) {

      die("Tree node expected.");

    }

  }

  public static function checkTreeNodeHasChildren($treeNode) {

    if (!$treeNode->hasChildren()) {

      die("Was expecting a tree node with children.");

    }

  }

  public static function checkTreeNodeHasNoChildren($treeNode) {

    if ($treeNode->hasChildren()) {

      die("Was expecting a tree node with no children.");

    }

  }

  public function getName() {

    return $this->name;

  }

  public function setName($name) {

   self::checkTreeNodeName($name);

   $this->name = $name;

  }

  public static function checkTreeNodeName($name) {

    if (!is_null($name) and !is_string($name)) {

      die("Was expecting a string or null for node name.");

    }

  }

  public static function checkTreeNodeNameNotNull($name) {

    if (!is_string($name)) {

      die("Was expecting a string for node name.");

    }

  }

  public function getValue() {

    return $this->value;

  }

  public function setValue($value) {

   self::checkTreeNodeValue($value);

   $this->value = $value;

  }

  public static function checkTreeNodeValue($value) {

    if (!is_null($value) and !is_string($value)) {

      die("Was expecting a string value for node value.");

    }

  }

  public function hasChildren() {

    return count($this->children) > 0;

  }

  public function getChildren() {

    return $this->children;

  }

  public function getChildCount() {

    return count($this->children);

  }

  public function setChildren($children) {

    $this->children = array();

    $this->addChildren($children);

  }

  public function setChildrenArgs() {

    $this->setChildren(func_get_args());

  }

  public function addChildren($children) {

    if (!is_array($children)) {

      die("Was expecting an array of child nodes.");

    }

    foreach ($children as $child) {

      $this->addChild($child);

    }

  }

  public function addChildrenArgs() {

    foreach (func_get_args() as $arg) {

      $this->addChild($arg);

    }

  }

  public function addChild($child) {

    self::checkTreeNode($child);

    $this->addTreeNodeChild($child);

  }

  protected function addTreeNodeChild($child) {

    $this->checkCanInsertChild($child);

    $this->children[] = $child;

  }

  private function checkCanInsertChild($child) {

    foreach ($this->children as $currentChild) {

      if (strcmp($currentChild->getName(), $child->getName()) == 0) {

        die("Inserting child node would result in duplicate child name among children.");

      }

    }

  }

  public function removeAllChildren() {

    $this->setChildren(array());

  }

  public function hasMatchingChildNode($childNode) {

    foreach ($this->getChildren() as $child) {

      if ($childNode === $child) {

        return true;

      }

    }

    return false;
 
  }

  public function getChildByIndex($childIndex) {

    $children = $this->getChildren();

    $numChildren = count($children);

    if (!is_int($childIndex) or $childIndex < 0 or $childIndex > $numChildren - 1) {

      die("Invalid child index.");

    }

    for ($index = 0; $index < $numChildren; $index++) {

      if ($index == $childIndex) {

        return $children[$index];

      }

    }

  }

  public function getChildByName($name) {

    self::checkTreeNodeNameNotNull($name);

    foreach ($this->getChildren() as $child) {

      if (strcmp($child->getName(), $name) == 0) {

        return $child;

      }

    }

    return null;

  }

  public function getTreeNodeByName($name, $traversalMode = TreeNodeIterator::TRAVERSAL_MODE_BFS) {

    self::checkTreeNodeNameNotNull($name);

    $treeNodeIterator = new TreeNodeIterator($this, $traversalMode);

    foreach ($treeNodeIterator as $nodeKey => $node) {

      if (strcmp($node->getName(), $name) == 0) {

        return $node;

      }

    }

    return null;

  }

  public function getTreeNodePath($treeNode, $traversalMode = TreeNodeIterator::TRAVERSAL_MODE_BFS) {

    $condition = function($treeNodeIterator) use ($treeNode) {

      return $treeNodeIterator->current() === $treeNode;

    };

    return getTreeNodePathByTargetCondition($treeNode, $condition, $traversalMode);

  }

  public function getTreeNodePathByTargetCondition($treeNode, $condition, $traversalMode = TreeNodeIterator::TRAVERSAL_MODE_BFS) {

    self::checkTreeNode($treeNode);

    $treeNodePath = array();

    if ($traversalMode = TreeNodeIterator::TRAVERSAL_MODE_DFS) {

      $previousLevel = -1;

      $treeNodeIterator = new TreeNodeIterator($this, TreeNodeIterator::TRAVERSAL_MODE_DFS);

      foreach ($treeNodeIterator as $nodeKey => $node) {

        // update partial path to target tree node

        $currentLevel = $treeNodeIterator->key()->getNodeLevel();

        if ($currentLevel > $previousLevel) {

          array_push($treeNodePath, $treeNodeIterator->current());

        } else if ($currentLevel < $previousLevel) {

          array_pop($treeNodePath);

        }

        $previousLevel = $currentLevel;

        if (call_user_func($condition, $treeNodeIterator)) {

          return $treeNodePath;

        }

      }

    } else if ($traversalMode = TreeNodeIterator::TRAVERSAL_MODE_BFS) {

      $previousLevel = -1;

      $treeNodeIterator = new TreeNodeIterator($this, TreeNodeIterator::TRAVERSAL_MODE_BFS);

      $levels = $array();

      foreach ($treeNodeIterator as $nodeKey => $node) {

        // update levels array

        $currentLevel = $treeNodeIterator->key()->getNodeLevel();

        if ($currentLevel > $previousLevel) {

          array_push($levels, array());

          $previousLevel = $currentLevel;

        }

        array_push($levels[count($levels) - 1], $node);

        if (call_user_func($condition, $treeNodeIterator)) {

          // construct tree node path from levels

          $childNode = $node;

          array_push($treeNodePath, $childNode);

      array_pop($levels);

      while (count($levels) > 0) {

        $level = $levels[count($levels) - 1];

            foreach ($level as $parentLevelNode) {

              if ($parentLevelNode->hasMatchingChildNode($childNode)) {

                array_unshift($treeNodePath, $parentLevelNode);

                $childNode = $parentLevelNode;

                array_pop($levels);

                continue;

          } else {

                die("ERROR: Child node not found while constructing path.\n");

              }

            }

      }

          return $treeNodePath;

        }

      }

    }

    return null;

  }

  public function setCustomData($customData) {

    $this->customData = $customData;

  }

  public function getCustomData() {

    return $this->customData;

  }

  public function __toString() {

    return "Node Name: " . $this->name . "; Node Value: " . $this->value . "; Children count: " . (is_null($this->children) ? 0 : count($this->children)) . ".\n";

  }

  public function showFullTree($traversalMode = TreeNodeIterator::TRAVERSAL_MODE_DFS) {

    $condition = function($treeNodeIterator) {

      return true;

    };

    $this->showSubTreeWhileNodeIteratorCondition($condition, $traversalMode);

  }

  public function showSubTreeByMaxNodeCount($maxNodeCount, $traversalMode = TreeNodeIterator::TRAVERSAL_MODE_DFS) {

    if (!is_int($maxNodeCount) or $maxNodeCount < 0) {

      die("Non-negative subtree node count expected.");

    }

    $condition = function($treeNodeIterator) use ($maxNodeCount) {

      static $count = 0;

      $countReached = ($count < $maxNodeCount);

      ++$count;

      return $countReached;

    };

    $this->showSubTreeWhileNodeIteratorCondition($condition, $traversalMode);

  }

  public function showSubTreeWhileNodeIteratorCondition($condition, $traversalMode = TreeNodeIterator::TRAVERSAL_MODE_DFS) {

    $treeNodeIterator = new TreeNodeIterator($this, $traversalMode);

    foreach ($treeNodeIterator as $nodeKey => $node) {

      if (!call_user_func($condition, $treeNodeIterator)) {

        break;

      }

      echo "Index: ", $nodeKey->getNodeIndex(), ". Level: ", $nodeKey->getNodeLevel(), ". ";

      for ($i = 0; $i < $nodeKey->getNodeLevel(); $i++) {

        echo "\t";

      }

      echo $node;

    }

  }

}

class ParentAwareTreeNodeFactory {

  public static function createStubNode() {

    return new ParentAwareTreeNode(null, null, array(), null);

  }

  public static function createNamelessNonleafNode($children, $parent) {

    $parentAwareTreeNode = new ParentAwareTreeNode(null, null, $children, $parent);

    TreeNode::checkTreeNodeHasChildren($parentAwareTreeNode);

    ParentAwareTreeNode::checkParentAwareTreeNodeHasNoParent($parentAwareTreeNode);

    return $parentAwareTreeNode;

  }

  public static function createNonLeafNode($name, $children, $parent) {

    $parentAwareTreeNode = new ParentAwareTreeNode($name, null, $children, $parent);

    TreeNode::checkTreeNodeHasChildren($parentAwareTreeNode);

    ParentAwareTreeNode::checkParentAwareTreeNodeHasParent($parentAwareTreeNode);

    return $parentAwareTreeNode;

  }

  public static function createLeafNode($name, $value, $parent) {

    $parentAwareTreeNode = new ParentAwareTreeNode($name, $value, array(), $parent);

    TreeNode::checkTreeNodeHasNoChildren($parentAwareTreeNode);

    ParentAwareTreeNode::checkParentAwareTreeNodeHasParent($parentAwareTreeNode);

    return $parentAwareTreeNode;

  }

  public static function createChildlessParentAwareTreeNodeFromTreeNode($treeNode, $parent) {

    $parentAwareTreeNode = new ParentAwareTreeNode($treeNode->getName(), $treeNode->getValue(), array(), $parent);

    if (!is_null($parent)) {

      $parentAwareTreeNode->setCustomData($parent->getCustomData());

    }

    return $parentAwareTreeNode;

  }

}

class ParentAwareTreeNode extends TreeNode {

  private $parent;

  public function __construct($name, $value, $children, $parent) {

    parent::__construct($name, $value, $children);

    $this->setParent($parent);

  }

  public static function checkParentAwareTreeNode($parentAwareTreeNode) {

    // check whether tree node is not null and is a class instance

    if (!is_a($parentAwareTreeNode, __CLASS__)) {

      die("Parent aware tree node expected.");

    }

  }

  public static function checkParentAwareTreeNodeOrNull($parentAwareTreeNode) {

    if (!is_null($parentAwareTreeNode) and !is_a($parentAwareTreeNode, __CLASS__)) {

      die("Parent aware tree node or null expected.");

    }

  }

  public static function checkParentAwareTreeNodeHasNoParent($parentAwareTreeNode) {

    if ($parentAwareTreeNode->hasParent()) {

      die("Was expecting a parent aware tree node with a parent node.");

    }

  }

  public static function checkParentAwareTreeNodeHasParent($parentAwareTreeNode) {

    if (!$treeNode->hasChildren()) {

      die("Was expecting a parent aware tree node with no children.");

    }

  }

  public function hasParent($parent) {

    return $this->parent != null;

  }

  public function setParent($parent) {

    self::checkParentAwareTreeNodeOrNull($parent);

    $this->parent = $parent;

  }

  public function getParent() {

    return $this->parent;

  }

  public function addChild($child) {

    self::checkParentAwareTreeNode($child);

    $this->addTreeNodeChild($child);

    $child->setParent($this);

  }

  public function __toString() {

    if (is_null($this->parent)) {

      $parentString = "No parent";

    } else {

      $parentName = $this->parent->getName();

      $parentString = "Parent name: " . (is_null($parentName) ? "nameless" : $parentName);

    }

    return $parentString . "; " . parent::__toString();

  }

}

class LabeledTreeNodeFactory {

  public static function createLabeledTreeNode($label, $children) {

    $treeNode = new TreeNode($label, null, $children);

    return $treeNode;

  }

}

/**
 * Iterator enabling BFS or DFS tree traversal (assumes validity of tree nodes has already been checked).
 */

class TreeNodeIterator implements Iterator {

  private $traversalMode;

  private $rootNode;

  private $nodesToVisit;

  private $nodesToVisitLevels;

  private $currentNodeIteratorKey;

  private $customData;

  const TRAVERSAL_MODE_BFS = 0;

  const TRAVERSAL_MODE_DFS = 1;

  const NUM_TRAVERSAL_MODES = 2;

  function __construct($rootNode, $traversalMode = self::TRAVERSAL_MODE_BFS) {

    $this->setTraversalMode($traversalMode);

    $this->setRootNode($rootNode);

    $this->rewind();

    $this->setCustomData(null);

  }

  private function setTraversalMode($traversalMode) {

    if (!is_integer($traversalMode) or $traversalMode < 0 or $traversalMode > self::NUM_TRAVERSAL_MODES) {

      die("Unsupported traversal mode specified.");

    }

    $this->traversalMode = $traversalMode;

  }

  private function setRootNode($rootNode) {

    $this->rootNode = $rootNode;

  }

  public function getRootNode() {

    return $this->rootNode;

  }

  /* methods from Iterator interface (and their helper methods) */

  public function current() {

    return $this->nodesToVisit[0];

  }

  public function key() {

    return $this->currentNodeIteratorKey;

  }

  public function next() {

   // update tracking data structures (must be done first)

    $this->planToVisitChildren();

    // pop current node from data structure tracking nodes to be visited

    array_shift($this->nodesToVisit);

    // pop current level from data structure tracking levels of nodes to be visited

    array_shift($this->nodesToVisitLevels);

    // check whether we have a current (first in nodes to visit array) node to visit and update current node index accordingly

    if (count($this->nodesToVisit) > 0) {

      // increment current node index

      $this->currentNodeIteratorKey->incrementNodeIndex();

      // increment or decrement current node level according to nodes to visit levels

      $this->currentNodeIteratorKey->setNodeLevel($this->nodesToVisitLevels[0]);

    } else {

      $this->currentNodeIteratorKey = null;

    }

  }

  /**
   * Call this method to plan to visit (further) children of current node.
   * If children are being added to the current node this method must be called before adding such children.
   */

  private function planToVisitChildren() {

    // retrieve visited node children

    $childrenToVisit = $this->nodesToVisit[0]->getChildren();
 
    if (count($childrenToVisit) > 0) {

      // remember (further) children to be visited

      switch ($this->traversalMode) {

        case self::TRAVERSAL_MODE_BFS:

          // remember (further) children to be visited last (BFS)

          $this->nodesToVisit = array_merge($this->nodesToVisit, $childrenToVisit);

          break;

        case self::TRAVERSAL_MODE_DFS:

          // remember (further) children to be visited first (DFS)

          array_splice($this->nodesToVisit, 1, 0, $childrenToVisit);

          break;

      }

      // retrieve node level at current tree node position

      $visitedNodeLevel = $this->nodesToVisitLevels[0];

      $visitedNodeChildrenLevel = $visitedNodeLevel + 1;

      $visitedNodeChildrenLevels = array_fill(0, count($childrenToVisit), $visitedNodeChildrenLevel);

      // remember level of (further) children to be visited

      switch ($this->traversalMode) {

        case self::TRAVERSAL_MODE_BFS:

          // remember levels of children to be visited last (BFS)

          $this->nodesToVisitLevels = array_merge($this->nodesToVisitLevels, $visitedNodeChildrenLevels);

          break;

        case self::TRAVERSAL_MODE_DFS:

          // remember levels of children to be visited first (DFS)

          array_splice($this->nodesToVisitLevels, 1, 0, $visitedNodeChildrenLevels);

          break;

      }

    }

  }

  public function rewind() {

    $this->nodesToVisit = array();

    $this->nodesToVisitLevels = array();

    $this->currentNodeIteratorKey = null;

    if (!is_null($this->rootNode)) {

      array_push($this->nodesToVisit, $this->rootNode);

      array_push($this->nodesToVisitLevels, 0);

      $this->currentNodeIteratorKey = new TreeNodeIteratorKey();

      $this->currentNodeIteratorKey->setNodeIndex(0);

      $this->currentNodeIteratorKey->setNodeLevel($this->nodesToVisitLevels[0]);

    }

  }

  public function valid() {

    return !is_null($this->currentNodeIteratorKey);

  }

  /* other methods */

  public function addChild($childTreeNode) {

    // (no tree traversal data structures related to extra children to be visited to update)

    // (having updated tree traversal data structures) add child tree node to current tree node children

    $this->current()->addChild($childTreeNode);

  }

  public function getNextNonLeafStepCount() {

    return $this->getStepCountToNextNodeIteratorCondition(self::getIteratorConditionIsNonLeafNode());

  }

  public function getNextLeafStepCount() {

    return $this->getStepCountToNextNodeIteratorCondition(self::getIteratorConditionIsLeafNode());

  }

  public function getStepCountToNextNodeIteratorCondition($condition) {

    $searchIterator = new TreeNodeIterator($this->current(), $this->traversalMode);

    if ($searchIterator->valid()) {

      $searchIterator->next();

      for ($stepCount = 1; $searchIterator->valid(); $stepCount++) {

        if (call_user_func($condition, $searchIterator)) {

          return $stepCount;

        }

        $searchIterator->next();

      }

    }

    return null;

  }

  public function nextCount($count) {

    for ($i = 0; $i < $count; $i++) {

      $this->checkValid();

      $this->next();

    }

  }

  public function nextNonLeaf() {

    return $this->nextCondition(self::getIteratorConditionIsNonLeafNode());

  }

  public function nextLeaf() {

    return $this->nextCondition(self::getIteratorConditionIsLeafNode());

  }

  public function nextMatch($treeNode) {

    return $this->nextCondition(self::getIteratorConditionNodeMatches($treeNode));

  }

  public function nextChildMatch($childNode) {

    return $this->nextCondition(self::getIteratorConditionHasMatchingChildNode($childNode));

  }

  public function nextCondition($condition) {

    $stepCount = 0;

    if ($this->valid()) {

      $this->next();

      ++$stepCount;

      while ($this->valid()) {

        if (call_user_func($condition, $this)) {

          break;

        }

        $this->next();

        ++$stepCount;

      }

    }

    return $stepCount;

  }

  private function checkValid() {

    if (!$this->valid()) {

      die("Tree node iterator already past last node in the tree.");

    }

  }

  private static function getIteratorConditionIsNonLeafNode() {

    return function($treeNodeIterator) {

      return $treeNodeIterator->current()->hasChildren();

    };

  }

  private static function getIteratorConditionIsLeafNode() {

    return function($treeNodeIterator) {

      return !$treeNodeIterator->current()->hasChildren();

    };

  }

  private static function getIteratorConditionNodeMatches($treeNode) {

    return function($treeNodeIterator) use ($treeNode) {

      return $treeNodeIterator->current() === $treeNode;

    };

  }

  private static function getIteratorConditionHasMatchingChildNode($childNode) {

    return function($treeNodeIterator) use ($childNode) {

      return $treeNodeIterator->current()->hasMatchingChildNode($childNode);

    };

  }

  public function setCustomData($customData) {

    $this->customData = $customData;

  }

  public function getCustomData() {

    return $this->customData;

  }

}

class TreeNodeIteratorKey {

  private $nodeIndex;

  private $nodeLevel;

  private $customData;

  function __construct() {

    $this->nodeIndex = null;

    $this->nodeLevel = null;

    $this->customData = null;

  }

  public function getNodeIndex() {

    return $this->nodeIndex;

  }

  public function setNodeIndex($nodeIndex) {

    $this->nodeIndex = $nodeIndex;

  }

  public function incrementNodeIndex() {

    $this->checkNodeIndex();

    ++$this->nodeIndex;

  }

  private function checkNodeIndex() {

    if (is_null($this->nodeIndex)) {

      die("Cannot increment null node index.");

    }

  }

  public function getNodeLevel() {

    return $this->nodeLevel;

  }

  public function setNodeLevel($nodeLevel) {

    $this->nodeLevel = $nodeLevel;

  }

  public function incrementNodeLevel() {

    $this->checkNodeLevel();

    ++$this->nodeLevel;

  }

  public function decrementNodeLevel() {

    $this->checkNodeLevel();

    --$this->nodeLevel;

  }

  private function checkNodeLevel() {

    if (is_null($this->nodeLevel)) {

      die("Cannot increment or decrement null node level.");

    }

  } 

  public function setCustomData($customData) {

    $this->customData = $customData;

  }

  public function getCustomData() {

    return $this->customData;

  }

}


© www.soinside.com 2019 - 2024. All rights reserved.