LoginSignup
18

More than 5 years have passed since last update.

汎用ツリー構造クラスを作ってみた

Last updated at Posted at 2015-09-10

何かとツリー構造が必要な場合があって、今までは配列で頑張ってました。
そろそろ、何とかしたいと思っいたってググってみても、探し方が悪いのかいい感じのものは見つからず。
そこで、面倒なのでサクッと作ってみました。
他にいいのがあれば教えてください。

ソース

TreeNode.php
/**
 * 汎用ツリー構造クラス
 */
<?php
class TreeNode
{
    /** 本オブジェクトの親要素 */
    private $parent;

    /** 本オブジェクトの子要素 */
    private $children = [];

    /** 名前 */
    private $name;

    /** 値 */
    public $value;

    /**
     * コンストラクタ
     *
     * @param string $name 名前(親の場合は省略可能)
     */
    public function __construct($name = null)
    {
        $this->name = $name;
    }

    /**
     * 子を追加する
     * すでに同名のものがある場合は追加しない
     *
     * @param string $name 追加する子要素
     * @return TreeNode 追加した子要素
     */
    public function append($name)
    {
        $child = $this->child($name);
        if ($child === false) {
            $child = new TreeNode($name);
            $child->parent = $this;
            $this->children[$child->name] = $child;
        }

        return $child;
    }

    /**
     * 指定した子を削除する
     *
     * @param string $name 削除する子要素
     * @return TreeNode 自分自身
     */
    public function remove($name)
    {
        if ($this->has($name)) {
            unset($this->children[$name]);
        }
        return $this;
    }

    /**
     * 指定した名前の子要素を取得する
     *
     * @param string $name 名前(省略時は全部)
     * @return TreeNode | array | boolean
     */
    public function child($name = null)
    {
        // 引数省略時は全部
        if (is_null($name)) {
            return $this->children;
        }

        // 引数指定時は指定した名前の子要素
        if (!$this->has($name)) {
            return false;
        }
        return $this->children[$name];
    }

    /**
     * 指定した名前の子要素があるかどうか
     * 省略時は子要素自体があるかどうか
     *
     * @param string $name 名前
     * @return boolean
     */
    public function has($name = null)
    {
        // 引数省略時
        if (is_null($name)) {
            return (0 < count($this->children));
        }
        return isset($this->children[$name]);
    }

    /**
     * 名前を取得
     * @return string
     */
    public function name()
    {
        return $this->name;
    }

    /**
     * 親要素を取得
     *
     * @return TreeNode 親要素
     */
    public function parent()
    {
        return $this->parent;
    }

    /**
     * パスの配列を取得する
     * valueは含まれません
     * 例: [NULL, 'HOGE', 'FUGA']
     *
     * @return array
     */
    public function path()
    {
        $result = [$this->name];

        $parent = $this->parent;
        while($parent != null) {
            $result[] = $parent->name;
            $parent = $parent->parent;
        }
        $result = array_reverse($result);
        return $result;
    }

}

使い方

 // 親ノード作成
$tree = new TreeNode();

// 子ノードを追加して子の値を設定
// 親 > HOGE = 123
$tree->append('HOGE')->value = 123;

// 子ノードを追加して、更にその下に子ノードを追加して
// 親 > HOGE > FUGA
$tree->append('HOGE')->append("FUGA");

// 子ノードHOGEの下にある子ノードFUGAを取得
$fuga = $tree->child('HOGE')->child('FUGA');

// 子ノードHOGEを追加して、それを削除(親ノードしか残ってない)
$tree->append('HOGE')->parent()->remove('HOGE')

// 「/」区切りのパス文字列を取得(/HOGE/FUGA)
echo implode('/', $tree->append('HOGE')->append('FUGA')->path());

おまけ

文字列から階層化されたものを作成してくれるメソッド追加

/**
 * 文字列をセパレータで分割したものをtree情報化する
 *
 * @param char $separator セパレータ文字
 * @param string $string 文字列
 * @return TreeNode TreeNode親要素
 */
public static function fromString($separator, $string)
{
    // セパレータが特殊文字の場合はエスケープ
    $sep = $separator;
    if ($sep == '/' || $sep == '\\') {
        $sep = '\\' . $sep;
    }

    // 先頭のセパレータを除去
    $string = preg_replace('/^' . $sep . '/', '', $string);

    $list = explode($separator, $string);

    $tree = null;
    foreach ($list as $str) {
        if ($tree === null) {
            $tree = new TreeNode($str);
        } else {
            $tree = $tree->append($str);
        }
    }
    return $tree;
}

使い方

$tree = TreeNode('/', 'hoge/fuga');
var_dump($tree->path())
// array(2) { [0]=> string(4) "hoge" [1]=> string(4) "fuga" }

$tree = TreeNode('/', '/hoge/fuga');
var_dump($tree->path())
// array(3) { [0]=> string(0) "" [1]=> string(4) "hoge" [2]=> string(4) "fuga" }

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18