Viewed   153 times

I have an array with the following keys

id   
parent_id
name

A sample array:

array(7) {
  [0]=>
  array(3) {
    ["id"]=>
    string(1) "4"
    ["parent_id"]=>
    string(1) "0"
    ["name"]=>
    string(16) "Top Level Page 4"
  }
  [1]=>
  array(3) {
    ["id"]=>
    string(1) "5"
    ["parent_id"]=>
    string(1) "1"
    ["name"]=>
    string(19) "Second Level Page 1"
  }
  [2]=>
  array(3) {
    ["id"]=>
    string(1) "6"
    ["parent_id"]=>
    string(1) "2"
    ["name"]=>
    string(19) "Second Level Page 2"
  }
  [3]=>
  array(3) {
    ["id"]=>
    string(1) "7"
    ["parent_id"]=>
    string(1) "5"
    ["name"]=>
    string(18) "Third Level Page 1"
  }
  [4]=>
  array(3) {
    ["id"]=>
    string(1) "3"
    ["parent_id"]=>
    string(1) "0"
    ["name"]=>
    string(16) "Top Level Page 3"
  }
  [5]=>
  array(3) {
    ["id"]=>
    string(1) "2"
    ["parent_id"]=>
    string(1) "0"
    ["name"]=>
    string(16) "Top Level Page 2"
  }
  [6]=>
  array(3) {
    ["id"]=>
    string(1) "1"
    ["parent_id"]=>
    string(1) "0"
    ["name"]=>
    string(16) "Top Level Page 1"
  }
}

What I would like to do is display a hierarchy tree using this array, the code I have at the moment is producing:

Top Level Page 4
--Second Level Page 1
---Second Level Page 2
----Third Level Page 1
Top Level Page 3
Top Level Page 2
Top Level Page 1

Ideally I need to produce the below result but with unlimited levels:

Top Level Page 4
-Second Level Page 1
-Second Level Page 2
--Third Level Page 1
Top Level Page 3
Top Level Page 2
Top Level Page 1

The code I have so far is:

$level = 1;
        foreach ($data as $row) {
            if ($row['parent_id'] == 0) {
                echo $row['name'] . '<br/>';
            } else {
                $level++;
                foreach ($data as $m) {
                    if ($m['parent_id'] === $row['parent_id']) {
                        $c = 0;
                        $append = '';
                        while ($c < $level) {
                            $append.="-";
                            $c++;
                        }
                        echo $append . $row['name'] . '<br/>';
                    }
                }
            }
        }
    }

If anyone could give me some pointers on how to achieve this it would be much appreciated.

I found a solution here: Create nested list from PHP array for dropdown select field

 Answers

4

You should use recursion.

Here an exemple of code:

$datas = array(
    array('id' => 1, 'parent' => 0, 'name' => 'Page 1'),
    array('id' => 2, 'parent' => 1, 'name' => 'Page 1.1'),
    array('id' => 3, 'parent' => 2, 'name' => 'Page 1.1.1'),
    array('id' => 4, 'parent' => 3, 'name' => 'Page 1.1.1.1'),
    array('id' => 5, 'parent' => 3, 'name' => 'Page 1.1.1.2'),
    array('id' => 6, 'parent' => 1, 'name' => 'Page 1.2'),
    array('id' => 7, 'parent' => 6, 'name' => 'Page 1.2.1'),
    array('id' => 8, 'parent' => 0, 'name' => 'Page 2'),
    array('id' => 9, 'parent' => 0, 'name' => 'Page 3'),
    array('id' => 10, 'parent' => 9, 'name' => 'Page 3.1'),
    array('id' => 11, 'parent' => 9, 'name' => 'Page 3.2'),
    array('id' => 12, 'parent' => 11, 'name' => 'Page 3.2.1'),
    );

function generatePageTree($datas, $parent = 0, $depth=0){
    $ni=count($datas);
    if($ni === 0 || $depth > 1000) return ''; // Make sure not to have an endless recursion
    $tree = '<ul>';
    for($i=0; $i < $ni; $i++){
        if($datas[$i]['parent'] == $parent){
            $tree .= '<li>';
            $tree .= $datas[$i]['name'];
            $tree .= generatePageTree($datas, $datas[$i]['id'], $depth+1);
            $tree .= '</li>';
        }
    }
    $tree .= '</ul>';
    return $tree;
}

echo(generatePageTree($datas));

You can test it at: http://phpfiddle.org/main/code/1qy-5fj

Or if you want the exact format:

function generatePageTree($datas, $parent = 0, $depth = 0){
    $ni=count($datas);
    if($ni === 0 || $depth > 1000) return ''; // Make sure not to have an endless recursion
    $tree = '';
    for($i=0; $i < $ni; $i++){
        if($datas[$i]['parent'] == $parent){
            $tree .= str_repeat('-', $depth);
            $tree .= $datas[$i]['name'] . '<br/>';
            $tree .= generatePageTree($datas, $datas[$i]['id'], $depth+1);
        }
    }
    return $tree;
}

The test: http://phpfiddle.org/main/code/jw3-s1j

Friday, October 28, 2022
 
1

Actually, this can be done. Through a php extension.

File: config.m4

PHP_ARG_ENABLE(test, whether to enable test Extension support, [ --enable-test   Enable test ext support])

if test "$PHP_TEST" = "yes"; then
  AC_DEFINE(HAVE_TEST, 1, [Enable TEST Extension])
  PHP_NEW_EXTENSION(test, test.c, $ext_shared)
fi

File: php_test.h

#ifndef PHP_TEST_H
#define PHP_TEST_H 1

#define PHP_TEST_EXT_VERSION "1.0"
#define PHP_TEST_EXT_EXTNAME "test"

PHP_FUNCTION(getaddress4);
PHP_FUNCTION(getaddress);

extern zend_module_entry test_module_entry;
#define phpext_test_ptr &test_module_entry

#endif

File: test.c

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_test.h"

ZEND_BEGIN_ARG_INFO_EX(func_args, 1, 0, 0)
ZEND_END_ARG_INFO()

static function_entry test_functions[] = {
    PHP_FE(getaddress4, func_args)
    PHP_FE(getaddress, func_args)
    {NULL, NULL, NULL}
};

zend_module_entry test_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_TEST_EXT_EXTNAME,
    test_functions,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_TEST_EXT_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_TEST
ZEND_GET_MODULE(test)
#endif

PHP_FUNCTION(getaddress4)
{
    zval *var1;
    zval *var2;
    zval *var3;
    zval *var4;
    char r[500];
    if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "aaaa", &var1, &var2, &var3, &var4) == FAILURE ) {
      RETURN_NULL();
    }
    sprintf(r, "n%p - %p - %p - %pn%p - %p - %p - %p", var1, var2, var3, var4, Z_ARRVAL_P(var1), Z_ARRVAL_P(var2), Z_ARRVAL_P(var3), Z_ARRVAL_P(var4) );
    RETURN_STRING(r, 1);
}

PHP_FUNCTION(getaddress)
{
    zval *var;
    char r[100];
    if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &var) == FAILURE ) {
      RETURN_NULL();
    }
    sprintf(r, "%p", Z_ARRVAL_P(var));
    RETURN_STRING(r, 1);
}

Then all you have to do is phpize it, config it, and make it. Add a "extension=/path/to/so/file/modules/test.so" to your php.ini file. And finally, restart the web server, just in case.

<?php
  $x = array("123"=>"123");
  $w = $x;
  $y = $x;
  $z = &$x;
  var_dump(getaddress4($w,$x,$y,$z));
  var_dump(getaddress($w));
  var_dump(getaddress($x));
  var_dump(getaddress($y));
  var_dump(getaddress($z));
?>

Returns(at least for me, your memory addresses will probably be different)

string '
0x9efeb0 - 0x9effe0 - 0x9ef8c0 - 0x9efeb0
0x9efee0 - 0x9f0010 - 0x9ed790 - 0x9efee0' (length=84)

string '0x9efee0' (length=8)

string '0x9f0010' (length=8)

string '0x9ed790' (length=8)

string '0x9efee0' (length=8)

Thanks to Artefacto for pointing this out, but my original code was passing the arrays by value, so thereby was recreating arrays including the referenced-one, and giving you bad memory values. I have since changed the code to force all params to be passed by reference. This will allow references, arrays, and object, to be passed in unmolested by the php engine. $w/$z are the same thing, but $w/$x/$y are not. The old code, actually showed the reference breakage and the fact that the memory addresses would change or match when all variables were passed in vs multiple calls to the same function. This was because PHP would reuse the same memory when doing multiple calls. Comparing the results of the original function would be useless. The new code should fix this problem.

FYI - I'm using php 5.3.2.

Sunday, September 4, 2022
 
sk0x50
 
3

You can try below code to merge array. Code generates desired output required to you. I have used sample array as given by you:

<?php
    $arr1=array(
        "384"=>array("name"=>"SomeMovieName1","age"=>"12.2 hrs","IMDBLink"=>"","IMDBRating"=>"", "coverArt"=>""),
        "452"=>array("name"=>"SomeMovieName2","age"=>"15.2 hrs","IMDBLink"=>"","IMDBRating"=>"", "coverArt"=>""),
        "954"=>array("name"=>"SomeMovieName3","age"=>"4.2 hrs","IMDBLink"=>"","IMDBRating"=>"", "coverArt"=>"")
    );
    $arr2=array(
       "384" => array("IMDBLink" => "7.2", "IMDBRating" => "http://www.imdb.com/LinkToMovie1", "coverArt" => "http://www.SomeLinkToCoverArt.com/1"),
       "452" => array("IMDBLink" => "5","IMDBRating" => "http://www.imdb.com/LinkToMovie2", "coverArt" => "http://www.SomeLinkToCoverArt.com/2"),
       "954"=>array("IMDBLink" => "8","IMDBRating" => "http://www.imdb.com/LinkToMovie3", "coverArt" => "http://www.SomeLinkToCoverArt.com/3")
    );
    $arr3 = array();
    foreach($arr1 as $key=>$val)
    {
         $arr3[] = array_merge($val, $arr2[$key]);
    }
    echo "<pre>";
    print_r($arr3);
?>
Tuesday, September 13, 2022
5

Here is some code to handle what you had originally proposed as output.

/**
 * Give it and array, and an array of parents, it will decent into the
 * nested arrays and set the value.
 */
function set_nested_value(array &$arr, array $ancestors, $value) {
  $current = &$arr;
  foreach ($ancestors as $key) {

    // To handle the original input, if an item is not an array, 
    // replace it with an array with the value as the first item.
    if (!is_array($current)) {
      $current = array( $current);
    }

    if (!array_key_exists($key, $current)) {
      $current[$key] = array();
    }
    $current = &$current[$key];
  }

  $current = $value;
}


$education = array(
  'x[1]'     => 'Georgia Tech',
  'x[1][1]'  => 'Mechanical Engineering',
  'x[1][2]'  => 'Computer Science',
  'x[2]'     => 'Agnes Scott',
  'x[2][1]'  => 'Religious History',
  'x[2][2]'  => 'Women's Studies',
  'x[3]'     => 'Georgia State',
  'x[3][1]'  => 'Business Administration',
);

$neweducation = array();

foreach ($education as $path => $value) {
  $ancestors = explode('][', substr($path, 2, -1));
  set_nested_value($neweducation, $ancestors, $value);
}

Basically, split your array keys into a nice array of ancestor keys, then use a nice function to decent into the $neweducation array using those parents, and set the value.

If you want the output that you have updated your post to have, add this in the foreach loop after the line with 'explode'.

$ancestors[] = 0;
Thursday, December 22, 2022
 
colacx
 
5

My solution:

$data = array(
    array('id' => 1, 'parent_id' => null, 'name' => 'lorem ipsum'),
    array('id' => 2, 'parent_id' => 1, 'name' => 'lorem ipsum1'),
    array('id' => 3, 'parent_id' => 1, 'name' => 'lorem ipsum2'),
    array('id' => 4, 'parent_id' => 2, 'name' => 'lorem ipsum3'),
    array('id' => 5, 'parent_id' => 3, 'name' => 'lorem ipsum4'),
    array('id' => 6, 'parent_id' => null, 'name' => 'lorem ipsum5'),
);

$itemsByReference = array();

// Build array of item references:
foreach($data as $key => &$item) {
   $itemsByReference[$item['id']] = &$item;
   // Children array:
   $itemsByReference[$item['id']]['children'] = array();
   // Empty data class (so that json_encode adds "data: {}" ) 
   $itemsByReference[$item['id']]['data'] = new StdClass();
}

// Set items as children of the relevant parent item.
foreach($data as $key => &$item)
   if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
      $itemsByReference [$item['parent_id']]['children'][] = &$item;

// Remove items that were added to parents elsewhere:
foreach($data as $key => &$item) {
   if($item['parent_id'] && isset($itemsByReference[$item['parent_id']]))
      unset($data[$key]);
}

// Encode:
$json = json_encode($data);
Sunday, September 18, 2022
 
ecaroth
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :