在php

时间:2015-05-18 09:31:26

标签: php tree hierarchical-data

您好我正在尝试打印类别和他们的孩子像db的树路径。直到现在我能够打印如下所示的树形结构数组。

Array
(
[0] => Array
    (
        [name] => 'Furniture'
        [slug] => 'furniture'
        [children] => Array
            (
                [0] => Array
                    (
                        [name] => 'Sofa'
                        [slug] => 'sofa'
                        [leafs] => Array
                            (
                               [0] => Array ( [name] => '3 Seater', [slug] = '3-seater'
                               [1] => Array ( [name] => '4 Seater', [slug] = '4-seater'
                            )

                    )

                [1] => Array
                    (
                        [name] => 'Chairs'
                        [slug] => 'chairs'
                        [leafs] => Array
                            (
                               [0] => Array ( [name] => '3 Seater', [slug] = '3-seater'
                               [1] => Array ( [name] => '4 Seater', [slug] = '4-seater'
                            )

                    )

            )

    )

[1] => Array
    (
        [name] => 'Furniture1'
        [slug] => 'furniture1'
        [children] => Array
            (
                [0] => Array
                    (
                        [name] => 'Sofa1'
                        [slug] => 'sofa1'
                        [leafs] => Array
                            (
                               [0] => Array ( [name] => '3 Seater1', [slug] = '3-seater1'
                               [1] => Array ( [name] => '4 Seater1', [slug] = '4-seater1'
                            )

                    )

                [1] => Array
                    (
                        [name] => 'Chairs1'
                        [slug] => 'chairs1'
                        [leafs] => Array
                            (
                               [0] => Array ( [name] => '3 Seater1', [slug] = '3-seater1'
                               [1] => Array ( [name] => '4 Seater1', [slug] = '4-seater1'
                            )

                    )

            )

    )

)

有些孩子可能有叶子或有些父母可能有孩子。但是我想要打印的内容如下所示。

Array(
[0] => 'Furniture/Sofa/3 Seater',
[1] => 'Furniture/Sofa/4 Seater',
[2] => 'Furniture/Chiars/ 3 Seater'
[3] => 'Furniture/Chiars/4 Seater',
[4] => 'Furniture1/Sofa1/3 Seater1',
[5] => 'Furniture1/Sofa/4 Seater1',
[6] => 'Furniture1/Chiars1/ 3 Seater1'
[7] => 'Furniture1/Chiars1/4 Seater1',
);

3 个答案:

答案 0 :(得分:2)

这是一个多路树'。

更新:这是代码TreePaths的完整重做

Original full source code at Pastebin.com - Execute at sandbox.onlinephpfunctions.com

这个答案描述了新代码。

变化:

  • 输入array node现在只需要children条目而不是' leafs`。这使得输入更加一致。

  • 此代码检查的array node中唯一的条目是$node['children]。这允许您在节点中拥有任何其他数据,您可以以callable以任何方式处理它。

  • 节点处理可以由具有以下符号的任何callable替换:

    function(array $currentPath, /* boolean */ $isTopALeaf) {...

  • 如果nodeProcessor(可调用)返回非空值,则它将被添加到$allPaths数组中。

外部资源和演示

网站:

描述

有趣的是,我们必须存储“路径”。到叶子'节点。 '路径'似乎是特别的'。但是,想象一下每次你都在追逐'或者'筑巢'在一个级别,然后你记录你在堆栈的位置'。 当你到达结束时当前列表你:

  • 通过'堆栈记录路径'到当前叶子节点'
  • 致电nodeProcessor
  • ' UNNEST'或者' pop'堆栈的顶部。

我使用'树结构'和提供的名称。

在每个路径的末尾,使用nodeProcessor

调用the current path

输出:

  • '完整路径'对任何特定的叶子'节点
  • 每个叶子的所有路径'节点

因为它是一棵树,所以递归'需要算法作为深度'树的未知数。

该计划必须:

  • 遍历'
  • 维持一个堆栈'节点名称作为'路径'到当前节点
  • 根据需要在所选节点上运行用户代码。我使用了一个'关闭'在此代码中为此。
  • 将输出存储在有用的位置。

节点处理器(可调用) - 替换默认值

/*
 * Generate an HTML anchor tag from the 'slug' entries
 */
$slugNodeProcessor =
    function(array $currentPath,
                     $isTopALeaf)  {

        /*
         * Lets make some HTML anchors for the Slugs?
         */
        $template = '<a href="%s" class="Slug">%s</a>';

        // top of the stack will be a leaf
        $title = '';
        $top  = end($currentPath); // easier to work with
        $prev = prev($currentPath); // need the description for title
        $title = $prev['name'] .' - '.  $top['name'];

        $url = '/';
        foreach($currentPath as $key => $node) {
                $url .= $node['Slug'] .'/';
        };
        $url = rtrim($url, '/');
        $htmlSlug = sprintf($template, $url, $title);

        return $htmlSlug;
     };

默认的&#39; nodeProcessor&#39;:

/**
     * If you don't provide a callable to generate paths then this will be used.
     *
     * It generates a string of names separated by '/'. i.e. it looks like a filepath.
     *
     * @return string
     */
    public function defaultNodeProcessor()
    {
        $dnp = function(array $currentPath,
                         $isTopALeaf)  {

                $fullPath = '/';
                foreach($currentPath as $key => $node) {
                    $fullPath .= $node['name'] .'/';
                }
                return rtrim($fullPath, '/');
             };
         return $dnp;
    }

运行程序:

$tree = new TreePaths($srcTree,
                      $slugNodeProcessor);
$tree->generate();
$allPaths = $tree->allPaths();

输出:

array (8) [
    string (67) "<a href="/furniture/sofa/3-seater" class="Slug">Sofa - 3 Seater</a>"
    string (67) "<a href="/furniture/sofa/4-seater" class="Slug">Sofa - 4 Seater</a>"
    string (76) "<a href="/furniture/sofa/chairs/3-seater" class="Slug">Chairs - 3 Seater</a>"
    string (76) "<a href="/furniture/sofa/chairs/4-seater" class="Slug">Chairs - 4 Seater</a>"
    string (94) "<a href="/furniture/sofa/chairs/furniture1/sofa1/3-seater1" class="Slug">Sofa1 - 3 Seater1</a>"
    string (94) "<a href="/furniture/sofa/chairs/furniture1/sofa1/4-seater1" class="Slug">Sofa1 - 4 Seater1</a>"
    string (104) "<a href="/furniture/sofa/chairs/furniture1/sofa1/chairs1/3-seater1" class="Slug">Chairs1 - 3 Seater1</a>"
    string (104) "<a href="/furniture/sofa/chairs/furniture1/sofa1/chairs1/4-seater1" class="Slug">Chairs1 - 4 Seater1</a>"
]

班级:

/*
 * Source Tree:
 *   
 * Tree Node:
 *
 * Array(
 *        "name" => 'Furniture',  // not checked
 *        "slug" => 'furniture',  // optional - not used
 *        "children" => Array( // will be other Tree nodes...
 *                          ),
 *      );
 *
 *  The `children` key is optional, if empty or missing, means it is a `leaf` node
 *
 *  !!! Note: The only array entry checked in here is 'children' !!!
 *
 *  But you will need to overide the default nodeProcessor.
 *
 *  The default `nodeProcessor` uses `name` and `children` only
 */

/*
 * NodeProcessor:
 *   o It is a callable that accepts two parameters
 *     o current path - an array of all the nodes so far in this path
 *     o isTopALeaf   - is the end of the path a 'leaf' node?
 */

/**
 *  Traverse the tree of `nodes`
 *  Generate a list of Paths from Root to every leaf node as an array of `nodes`.
 *  It is a `stack` with the top node being a leaf.
 */
class TreePaths {

    /**
     * The 'current' menu / tree
     *
     * @var array $tree
     */
    private $tree = array();


    /**
     * The Output
     *
     * @var array $allPaths
     */
    private $allPaths = array();

    /**
     * The 'current' stack of nodes in this path
     *
     * This is a 'stack'. The 'path' is all the entries combined.
     *
     * @var array $currentPath
     */
    private $currentPath = array();


    /**
     * The 'callable' to be run for nodes
     *
     * @var callable $nodeProcessor
     */
    private $nodeProcessor = null;


    /**
     * Call All Nodes or Leaf node only
     *
     * @var boolean
     */
    private $callLeafNodesOnly = true;


    /**
     * Build the class but do not run it...
     *
     * Provides a default NodeProcessor if you don't provide one.
     *    o The default processor builds string paths that look like filepaths
     *
     * @param array $tree
     * @param callable $processNode        - optional
     * @param boolean $callLeafNodesOnly  - optional default true
     */
    public function __construct(array $tree,
                                   /* callable */ $processNode = null,
                                   $callLeafNodesOnly = true)
    {
        $this->tree = $tree;
        $this->nodeProcessor = $processNode;
        $this->callLeafNodesOnly = $callLeafNodesOnly;

        // provide a default processor
        if (is_null($this->nodeProcessor)) {
            $this->nodeProcessor = $this->defaultNodeProcessor();
        }
    }

    /**
     * This routine makes this class rather powerful as you can use any callable.
     *
     * @param type $nodeProcessor
     */
    public function setNodeProcessor(/* callable */ $nodeProcessor)
    {
        $this->nodeProcessor = $nodeProcessor;
    }

    /**
     * Return a list of all the paths that were generated by the 'nodeProcessor'
     * @return array
     */
    public function allPaths()
    {
        return $this->allPaths;
    }

    /**
     * The routine that processes one node and recurses as required
     *
     * @param array $node
     * @return void This is all side-effects
     */
    protected function treeWalk($node)
    {
        // always add the node to the currentPath
        array_push($this->currentPath, $node);

        // Always call the node processor and add the path to all paths if required
        $processedPath = $this->callNodeProcessor($this->currentPath,
                                                  $this->isLeafNode($node));
        if (!empty($processedPath)) { // add to all the paths
            $this->allPaths[] = $processedPath;
        }

        // do we recurse?
        if ($this->isLeafNode($node)) { // no we dont...
            array_pop($this->currentPath); // lose leaf node from top of stack
            return; // nothing more to do
        }

        // now process all the children... This will recurse - always
        foreach ($node['children'] as $key => $node) {
            $this->treeWalk($node);
        }
        return; // end of children
    }

    /**
     * Process all the top level nodes.
     *
     * @return void
     */
    public function generate()
    {
        $this->allPaths = array();

        foreach ($this->tree as $key => $node) {
            $this->treeWalk($node);
        }
        return;
    }

    /**
     * End of a path?
     *
     * @param array $node
     * @return boolean
     */
    protected function isLeafNode($node)
    {
        return empty($node['children']);
    }

    /**
     * Are we in the 'middle' of a path?
     *
     * @param array $node
     * @return boolean
     */
    protected function hasChildren($node)
    {
        return !empty($node['children']);
    }


    /**
     * The `callable` to be called.
     *
     * It must accept the two parameters.
     *
     * It can be set after the 'class instance' is created.
     *
     * @param array currentPath to this value
     * @param string nodeType - leaf or children
     *
     * @return mixed if not empty will be added to the paths
     */
    protected function callNodeProcessor(array $currentPath,
                                             $isTopALeaf)
    {

        if ($this->callLeafNodesOnly) {
            if ($isTopALeaf)  {
                return call_user_func($this->nodeProcessor,
                                       $currentPath,
                                       $isTopALeaf);
            }
        }
        else {
            return call_user_func($this->nodeProcessor,
                                   $currentPath,
                                   $isTopALeaf);
        }
    }

    /**
     * If you don't provide a callable to generate paths then this will be used.
     *
     * It generates a string of names separated by '/'. i.e. it looks like a filepath.
     *
     * @return string
     */
    public function defaultNodeProcessor()
    {
        $dnp = function(array $currentPath,
                         $isTopALeaf)  {

                $fullPath = '/';
                foreach($currentPath as $key => $node) {
                    $fullPath .= $node['name'] .'/';
                }
                return rtrim($fullPath, '/');
             };
         return $dnp;
    }
}

源数据格式:

/*
 * Tree Node:
 *
 * Array(
 *        "name" => 'Furniture',
 *        "slug" => 'furniture',
 *        "children" => Array( // can be other Tree nodes...
 *                          ),
 *      );
 *
 *  The `children` key is optional, if empty or missing, means it is a `leaf` node
 *
 *  !!! Note: The only array entry checked in here is 'children' !!!
 *
 *  But you would need to overide the default nodeProcessor.
 */

样本测试数据

$srcTree = Array(
  0 => Array(
          "name" => 'Furniture',
          "Slug" => 'furniture',
          "children" => Array(
                  "0" => Array
                      (
                          "name" => 'Sofa',
                          "Slug" => 'sofa',
                          "children" => Array
                              (
                                 "0" => Array ( "name" => '3 Seater', "Slug" => '3-seater'),
                                 "1" => Array ( "name" => '4 Seater', "Slug" => '4-seater'),
                              ),

                      ),

                  "1" => Array
                      (
                          "name" => 'Chairs',
                          "Slug" => 'chairs',
                          "children" => Array
                              (
                                 "0" => Array ( "name" => '3 Seater', "Slug" => '3-seater'),
                                 "1" => Array ( "name" => '4 Seater', "Slug" => '4-seater'),
                              )

                      )

              )

      ),
      More entries here ...

答案 1 :(得分:0)

$fullname = array();
$mainarrs = array(); //this is the array that will have your all the array data
$i=0;
foreach ( $mainarrs as $mainarr )
{

$temp = $mainarr['name'];

    foreach($mainarr as $anmain)
    {
        $temp2 = $anmain['name'];

        foreach($anmain as $lastmain)
        {
            $fullname[$i] = $temp."/".$temp2."/".$lastmain['name']
        }
    }
}

//now for the output that you expect to see is in $fullname array, you can loop through it to see the result.

我希望这会对你有所帮助。

答案 2 :(得分:0)

基于此处设置的数据:https://pastebin.com/CGPniY8C 我是通过这种解决方案来解决的:

class Controller():
    if getattr(sys, 'frozen', False):
        path = os.path.dirname(sys.executable)
    elif __file__:
        path = os.path.dirname(__file__)
    else:
        print('No path has been defined')

    windowWidth = 800
    windowHeight = 600

    def __init__(self):
        self._running = True
        self.sprites = pygame.sprite.Group()
        self.callback = None
    def on_init(self):
        pygame.init()
        pygame.display.set_caption('Ourstory')
        self.clock = pygame.time.Clock()
        self.screen = pygame.display.set_mode((self.windowWidth, self.windowHeight))
        self.menu('MainMenu')
        self._running = True
    def on_event(self, event):
        if event.type == QUIT:
            self._running = False
        elif event.type == TURN:
            self.turn += 1
    def on_loop(self):
        pass
    def on_cleanup(self):
        pygame.quit()
    def on_execute(self):
        if self.on_init() == False:
            self._running = False
        while (self._running):
            self.clock.tick(60)
            pygame.event.pump()
            #self.update_button()
            #self.sprites._update_image(events)
            self.screen.fill(pygame.Color('white'))
            self.sprites.draw(self.screen)
            pygame.display.update()
            keys = pygame.key.get_pressed()
            if (keys[K_ESCAPE]):
                self._running = False
            self.on_loop()
        self.on_cleanup()
    def menu(self, name:str):
            if name == 'MainMenu':
                background = Image._create_image(self, 'TOC_EarthDawn800600.png', 0, 0)
                self.sprites.add(background)

                #self.sprites.add(Controller.load_image(self, 'TOC_EarthDawn800600.png', 0, 0))
                ##Controller.load_button(self, 'SelectCivilisation', 'Culture3232.png', 'Espionage3232.png', 32, 32, 32, 32, -1, 'Select Civilisation', 'freesansbold.ttf', 8, (0,0,0))
                #self.sprites.add(Button('TestButton', (128,128,128), (160,160,160), (0,0,0), pygame.Rect(150, 200, 90, 100), QUIT))#, 'text', 'freesansbold.ttf', 8, (0,0,0)))
            elif name == 'NewGame':
                Controller.load_image(self, 'TOC_EarthDusk800600.png', 0, 0)
            else:
                print('Cannot find menu: ', name)

class Image(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
    def _create_image(self, name, x, y, colorkey=None):
        self.name = name
        fullname = os.path.join('Images', name)
        try:
            image = pygame.image.load(fullname)
        except:
            pygame.error
            print('Cannot find image: ', name)
        image = image.convert()
        if colorkey is not None:
            if colorkey is -1:
                colorkey = image.get_at((0, 0))  # i.e. top-left pixel
            image.set_colorkey(colorkey, RLEACCEL)
        return image

class Button(pygame.sprite.Sprite):
    def __init__(self, name:str, colourinactive:str, colouractive:str, colouroutline:str, rect, callback, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0), imageinactive=None, imageactive=None, colorkey=None):
        super().__init__()
        self.text = text
        temprect = pygame.Rect(0, 0, *rect.size)

        if not imageinactive:
            self.inactive = self._create_image(colourinactive, colouroutline, temprect, text)
            self.active = self._create_image(colouractive, colouroutline, temprect, text)
            self.image = self.inactive
            self.rect = rect
        self.callback = callback
    def _create_image(self, colourinactive:str, colouroutline:str, rect, text=None, font='freesansbold.ttf', fontsize=8, textcolour=(0,0,0)):
        imagesurface = pygame.Surface(rect.size)
        if colouroutline:
            imagesurface.fill(colouroutline)
            imagesurface.fill(colourinactive, rect.inflate(-4, -4))
        else:
            imagesurface.fill(colourinactive)
        if text:
            font = pygame.font.Font(font, fontsize)
            print(type(textcolour))
            textsurface = font.render(text, True, pygame.Color(textcolour))
            textrect = textsurface.get_rect(center=rect.center)
            img.blit(textsurface, textrect)
        return imagesurface
    def _update_image(self, events):
        mouse = pygame.mouse.get_pos()
        active = self.rect.collidepoint(mouse)
        self.image = self.active if active else self.inactive
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN and hit:
                self.callback(self)

输出:

/* Initialize dataset */
$srcTree = Array(...dataset_pastebin...);

/* 
$children = List of children
$node_path = Original node path
$node_path_storage = Array to store all path found
*/
function getPathFromTree($children, $node_path, &$node_path_storage){
    $original_node_path = $node_path;
    foreach ($children as $child){
        $node_path = $original_node_path . '/' . $child['Slug'];
        array_push($node_path_storage, $node_path);
        if(!empty($child['children'])){
            getPathFromTree($child['children'], $node_path, $node_path_storage);
        }
    }
}

/* Usage */
$node_path_storage = array();
getPathFromTree($srcTree, '', $node_path_storage);
echo '<pre>' . print_r($node_path_storage, true) . '</pre>';