I have an array of id's which are formed into a tree structure, for a storage type application.
The tree structure is then displayed similar to this
What I am looking to do is create a list of the full path of each array element.
[366 => 'Files',356 => 'Misc',354 => 'Photos',368 = 'Photos/Cities',375 = 'Photos/Cities/England',376 = 'Photos/Cities/Scotland']
The menu is user defined so this is just an example, it could have many more levels.
The name for each array element is added from an array of names ie names[376] (Photos)
I have tried several recursive functions and struggled, I'm hopeing someone who is much better at PHP than me can help!
Thanks
I hope I understand what you want to achieve. In that case, this could be a solution
<?php
$ids = [
354 => [
368 => [
375,
376
]
],
356,
366
];
$names = [
354 => "Photos",
368 => "Cities",
375 => "England",
376 => "Scotland",
356 => "Files",
366 => "Misc"
];
print_r(build_list($ids));
function build_list($ids, $path = ""){
global $names;
$list = [];
foreach($ids as $key => $value){
if(is_array($value)){
//$list[$key] = $path . $names[$key]; // uncomment if you need output (2)
$list = array_replace_recursive($list, build_list($value, ($path . $names[$key] . "/")))
}else{
$list[$value] = $path . $names[$value];
}
}
return $list;
}
?>
Output (1)
Array
(
[375] => Photos/Cities/England
[376] => Photos/Cities/Scotland
[356] => Files
[366] => Misc
)
Output (2)
Array
(
[354] => Photos
[368] => Photos/Cities
[375] => Photos/Cities/England
[376] => Photos/Cities/Scotland
[356] => Files
[366] => Misc
)
This function will do what you want. It recursively traverses the tree, creating elements for each key and passing prefixes down through the recursion to create each element's name:
function make_paths($array, $names, $prefix = '') {
$output = array();
foreach ($array as $key => $arr) {
$name = $prefix . ($prefix != '' ? '/' : '') . $names[$key];
if (count($arr)) {
$output = $output + make_paths($arr, $names, $name);
}
$output[$key] = $name;
}
return $output;
}
Output:
Array (
[375] => Photos/Cities/England
[376] => Photos/Cities/Scotland
[368] => Photos/Cities
[354] => Photos
[356] => Misc
[366] => Files
)
Demo on 3v4l.org
Related
For four days I am trying to figure out how to solve this, as well as googling it, and was no luck
The problem is that I needed to loop through a nested array (for unknown deep) and keep the top-level keys (as a prefix to the last value) as long as I am still going deep, and then start over (the prefix need to reset) once it started a new path.
I want to generate complete addresses from this array.
$arr = [
"buildings" => [
"group1" => [
"b1" => [1,2,3,4],
"b2" => [1,2,3]
],
"group2" => [
"b1" => [1,2]
]
],
"villas" =>[
"group1" => [
"v1" => [1,2],
"v2" => [1]
],
"group2" => [
"v1" => [1],
"v2" => [1]
],
"group3" => [
"v1" => [1]
],
]
];
This is the needed output
buildings/group1/b1/1
buildings/group1/b1/2
buildings/group1/b1/3
buildings/group1/b1/4
buildings/group1/b2/1
buildings/group1/b2/2
buildings/group1/b2/3
buildings/group2/b1/1
buildings/group2/b1/2
villas/group1/v1/1
villas/group1/v1/2
villas/group1/v2/1
villas/group2/v1/1
villas/group2/v2/1
villas/group3/v1/1
I tried this function but also it didn't bring the wanted results
function test($array, $path = ""){
foreach ($array as $key => $value) {
if (is_array($value)){
$path .= $key."/";
test($value, $path);
} else {
echo $path.$value."<br>";
}
}
}
test($arr);
UPDATE
I understood where was my mistake and I wanted to share with you my modification to my method after I fixed it.
function test($array, $path = ""){
foreach ($array as $key => $value) {
if (is_array($value)){
test($value, $path . $key . '/');
} else {
echo $path.$value."<br>";
}
}
}
And thanks to #Kai Steinke he's method is way better than mine, and here is some improvements just to make it look better.
function flatten(array $array, array $flattened = [], string $prefix = ''): array
{
foreach ($array as $key => $value) {
if (is_array($value)) {
$flattened = array_merge( flatten($value, $flattened, $prefix . $key . '/'));
continue;
}
$flattened[] = $prefix . $value;
}
return $flattened;
}
Here you go:
function flatten($arr, $prefix = '') {
$result = [];
foreach ($arr as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, flatten($value, $prefix . $key . '/'));
} else {
$result[] = $prefix . $value;
}
}
return $result;
}
// Usage
print_r(flatten($arr))
Returns an Array:
Array (
[0] => buildings/group1/b1/1
[1] => buildings/group1/b1/2
[2] => buildings/group1/b1/3
[3] => buildings/group1/b1/4
[4] => buildings/group1/b2/1
[5] => buildings/group1/b2/2
[6] => buildings/group1/b2/3
[7] => buildings/group2/b1/1
[8] => buildings/group2/b1/2
[9] => villas/group1/v1/1
[10] => villas/group1/v1/2
[11] => villas/group1/v2/1
[12] => villas/group2/v1/1
[13] => villas/group2/v2/1
[14] => villas/group3/v1/1
)
Build and preserve the path using all encountered keys until arriving at a deep non-array value, then append the value to the string, and push the full path into the result array.
Code: (Demo)
function flatten(array $array, string $path = ''): array
{
$result = [];
foreach ($array as $k => $v) {
array_push(
$result,
...(is_array($v) ? flatten($v, "$path$k/") : ["$path$v"])
);
}
return $result;
}
var_export(flatten($array));
A slightly related answer where a slash-delimited string was built as the hierarchical path to a search value.
I need a way to dynamically access values in a nested array using an index map. What i want to achieve is looping over an array with data and extract some values that can be in any level of the nesting and save it to a bi-dimensional array.
So far I've come up with the following code, which works quite well, but I was wondering if there is a more efficient way to do this.
<?php
// Sample data
$array = array();
$array[0]['code'] = "ABC123";
$array[0]['ship'] = array("name" => "Fortune", "code" => 'FA');
$array[0]['departure'] = array("port" => "Amsterdam", "code" => "AMS");
$array[0]['document'] = array("type" => "Passport", "data" => array("valid" => '2022-03-18', 'number' => 'AX123456') );
$array[1]['code'] = "QWERT67";
$array[1]['ship'] = array("name" => "Dream", "code" => 'DR');
$array[1]['departure'] = array("port" => "Barcelona", "code" => "BRC");
$array[1]['document'] = array("type" => "Passport", "data" => array("valid" => '2024-12-09', 'number' => 'DF908978') );
// map of indexes of $array I need in my final result array. The levels of the nested indexes is subdivided by ":"
$map = array("code", "ship:name", "departure:port", "document:type", "document:data:number");
$result = array();
// loop array for rows of data
foreach($array as $i => $row){
// loop map for indexes
foreach($map as $index){
// extract specific nested values from $row and save them in 2-dim array $result
$result[$i][$index] = xpath_array($index, $row);
}
}
// print out result
print_r($result);
// takes path to value in $array and returns given value
function xpath_array($xpath, $array){
$tmp = array();
// path is subdivded by ":"
$elems = explode(":", $xpath);
foreach($elems as $i => $elem){
// if first (or ony) iteration take root value from array and put it in $tmp
if($i == 0){
$tmp = $array[$elem];
}else{
// other iterations (if any) dig in deeper into the nested array until last item is reached
$tmp = $tmp[$elem];
}
}
// return found item (can be value or array)
return $tmp;
}
Any suggestion?
This was quite tricky for me, i used Recursive function, first we normalize array keys to obtain key as you want like this document:type, then we normalize array to obtain all at same level :
/**
* #param array $array
* #param string|null $key
*
* #return array
*/
function normalizeKey(array $array, ?string $key = ''): array
{
$result = [];
foreach ($array as $k => $v) {
$index = !empty($key) && !\is_numeric($key) ? $key.':'.$k : $k;
if (true === \is_array($v)) {
$result[$k] = normalizeKey($v, $index);
continue;
}
$result[$index] = $v;
}
return $result;
}
/**
* #param array $item
* #param int $level
*
* #return array
*/
function normalizeStructure(array $item, int $level = 0): array
{
foreach ($item as $k => $v) {
$level = isset($v['code']) ? 0 : $level;
if (true === \is_array($v) && 0 === $level) {
$item[$k] = normalizeStructure($v, ++$level);
continue;
}
if (true === \is_array($v) && 0 < $level) {
$item = \array_merge($item, normalizeStructure($v, ++$level));
unset($item[$k]);
continue;
}
}
return $item;
}
$data = normalizeStructure(normalizeKey($array));
I edited your data set to add more nests:
// Sample data
$array = array();
$array[0]['code'] = "ABC123";
$array[0]['ship'] = array("name" => "Fortune", "code" => 'FA');
$array[0]['departure'] = array("port" => "Amsterdam", "code" => "AMS");
$array[0]['document'] = array("type" => "Passport", "data" => array("valid" => '2022-03-18', 'number' => 'AX123456'));
$array[1]['code'] = "QWERT67";
$array[1]['ship'] = array("name" => "Dream", "code" => 'DR');
$array[1]['departure'] = array("port" => "Barcelona", "code" => "BRC");
$array[1]['document'] = array("type" => "Passport", "data" => array("valid" => '2024-12-09', 'number' => 'DF908978', 'check' => ['number' => '998', 'code' => 'itsWell', 'inception' => ['border' => 'finalInception']]));
With these data, you should finally receive this result:
/*
Array
(
[0] => Array
(
[code] => ABC123
[ship:name] => Fortune
[ship:code] => FA
[departure:port] => Amsterdam
[departure:code] => AMS
[document:type] => Passport
[document:data:valid] => 2022-03-18
[document:data:number] => AX123456
)
[1] => Array
(
[code] => QWERT67
[ship:name] => Dream
[ship:code] => DR
[departure:port] => Barcelona
[departure:code] => BRC
[document:type] => Passport
[document:data:valid] => 2024-12-09
[document:data:number] => DF908978
[document:data:check:number] => 998
[document:data:check:code] => itsWell
[document:data:check:inception:border] => finalInception
)
)
*/
Recursivity seems to be like Inception, everything is nested and you can lose your mind in 😆, mine was already lost in.
I have an array that looks like this:
$rowarray(
[0] => [PID] => 97162 [TID] => 340 [StatsID] => 49678
[1] => [PID] => 97165 [TID] => 340 [StatsID] => 49673
[2] => [PID] => 97167 [TID] => 340 [StatsID] => 49675
[3] => [PID] => 97162 [TID] => 340 [StatsID] => 49679
)
Then my code looks like this:
$cntr=0;
foreach($rowarray as $row)
{
echo "<tr><td>$row[PID] $row[TID] $row[StatsID] </td></tr>";
$cntr++;
}
Two things I want to do I want to be able not print the duplicates in the array but print the additional column that has a different value. So my desired output would look like this.
97162 340 49678 49679
97165 340 49673
97167 340 49675
I started out with the array_unique() but that only returned:
97162 340 49678
Assuming only the StatsID changes (not clear from the question)
$map = array();
foreach($rowarray as $row){
$k = $row["PID"] . '-' . $row["TID"];
if( !isset( $map[$k] ) ){
$map[$k] = array();
}
array_push( $map[$k], $row["StatsId"] );
}
foreach($map as $k=>$v){
$row = explode( '-', $k );
echo "<tr><td>$row[0] $row[1] " . implode( " ", $v ) . " </td></tr>";
}
Here's what I'd do:
Start by sorting the array (using usort to sort by PID, then by TID)
Initialize "last" variables ($last_PID and $last_TID). They will store the respective values in the loop
In the loop, first compare the "current" variables to the "last" ones, if they're the same then just echo the StatsID value.
If they're not the same, output the <tr> (but not the final </tr>, so the first part of the loop can add more StatsID values if necessary)
Still inside the loop, after outputting everything, update the "last" variables.
After the loop, output the final </tr>
This may not be optimal, but I'm pretty sure it'll work.
Transfer the $rowarray structure into a map of maps of arrays, like this:
$rowarray = array(
array('PID' => 97162, 'TID' => 340, 'StatsID' => 49678),
array('PID' => 97165, 'TID' => 340, 'StatsID' => 49673),
array('PID' => 97167, 'TID' => 340, 'StatsID' => 49675),
array('PID' => 97162, 'TID' => 340, 'StatsID' => 49679)
);
$keys = array();
foreach ($rowarray as $row) {
if (!is_array(#$keys[$row['TID']])) {
$keys[$rowarray['TID']] = array();
}
if (!is_array(#$keys[$row['TID']][$row['PID']])) {
$keys[$row['TID']][$row['PID']] = array();
}
$keys[$row['TID']][$row['PID']][] = $row['StatsID'];
}
foreach ($keys as $pid => $pid_arr) {
foreach ($pid_arr as $tid => $tid_arr) {
echo "<tr><td>$tid $pid " . implode(' ', $tid_arr) . "</td></tr>";
}
}
See this code in action
As far as I can tell, the only way to do this would be to loop through the array creating a new unique array as you go.
$unique = array();
foreach ($row as $value)
{
$key = $value['PID'];
if (isset($unique[$key]))
{
$unique[$key]['StatsID'] .= ' ' . $value['StatsID'];
}
else
{
$unique[$key] = $value;
}
}
Now, $unique would give you the results you're looking for and you can loop through the unique array and output your results (I also added your counter if needed):
$count = count($unique);
foreach ($unique as $row)
{
echo "<tr><td>{$row['PID']} {$row['TID']} {$row['StatsID']} </td></tr>";
}
I'm not even sure how to begin wording this question, but basically, I have an array, that looks like this:
Array
(
[0] => /
[1] => /404/
[2] => /abstracts/
[3] => /abstracts/edit/
[4] => /abstracts/review/
[5] => /abstracts/view/
[6] => /admin/
[7] => /admin/ads/
[8] => /admin/ads/clickcounter/
[9] => /admin/ads/delete/
[10] => /admin/ads/edit/
[11] => /admin/ads/list/
[12] => /admin/ads/new/
[13] => /admin/ads/sponsordelete/
[14] => /admin/ads/sponsoredit/
[15] => /admin/ads/sponsornew/
[16] => /admin/ads/stats/
[17] => /admin/boilerplates/
[18] => /admin/boilerplates/deleteboiler/
[19] => /admin/boilerplates/editboiler/
[20] => /admin/boilerplates/newboilerplate/
[21] => /admin/calendar/event/add/
[22] => /admin/calendar/event/copy/
)
And I need to 'reduce' / 'process' it into an array that looks like this:
Array
(
[''] => Array()
['404'] => Array()
['abstracts'] => Array
(
[''] => Array()
['edit'] => Array()
['review'] => Array()
['view'] => Array()
)
['admin'] => Array
(
['ads'] => Array
(
[''] => Array()
['clickcounter'] => Array()
['delete'] =>Array()
['edit'] => Array()
)
)
.....
.....
)
That, if manually initialized would look something like this:
$urlTree = array( '' => array(),
'404' => array(),
'abstracts'=> array( '' => array(),
'edit' => array(),
'review'=> array(),
'view' => array() ),
'admin' => array( 'ads'=> array( '' => array(),
'clickcounter'=> array(),
'delete' => array(),
'edit' => array() ) )
);
I usually stray away from asking straight up for a chunk of code on SO, but does anyone perhaps have any advice / code that can traverse my array and convert it to a hierarchy?
EDIT: Here is the bit I have right now, which, I know is pitifully small, I'm just blanking out today it seems.
function loadUrlData()
{
// hold the raw data, /blah/blah/
$urlData = array();
$res = sql::query( "SELECT DISTINCT(`url`) FROM `pages` ORDER BY `url` ASC" );
while( $row = sql::getarray( $res ) )
{
$urlData[] = explode( '/', substr( $row['url'], 1, -1 ) );
}
// populated, eventually, with the parent > child data
$treeData = array();
// a url
foreach( $urlData as $k=> $v )
{
// the url pieces
foreach( $v as $k2=> $v2 )
{
}
}
// $treeData eventually
return $urlData;
}
Looks rather easy. You want to loop through all lines (foreach), split them into parts (explode), loop through them (foreach) and categorize them.
Since you don't like asking for a chunk of code, I won't provide any.
Update
A very nice way to solve this is to reference the $urlTree (use &), loop through every part of the URL and keep updating a variable like $currentPosition to the current part in the URL tree. Because you use &, you can simply edit the array directly while still using a simple variable.
Update 2
This might work:
// a url
foreach( $urlData as $k=> $v )
{
$currentSection = &$treeData;
// the url pieces
foreach( $v as $k2=> $v2 )
{
if (!isset($currentSection[$v2])) {
$currentSection[$v2] = array();
}
$currentSection = &$currentSection[$v2];
}
}
I know you didn't ask for a chunk of code, but I'd just call this a petit serving:
$map = array();
foreach($urls as $url) {
$folders = explode('/', trim($url, '/'));
applyChain($map, $folders, array());
}
function applyChain(&$arr, $indexes, $value) { //Here's your recursion
if(!is_array($indexes)) {
return;
}
if(count($indexes) == 0) {
$arr = $value;
} else {
applyChain($arr[array_shift($indexes)], $indexes, $value);
}
}
It's fairly simple. We separate each url into its folders (removing trailing and leading slashes) and then work our way down the array chain until we reach the folder mentioned in the URL. Then we place a new empty array there and continue to the next URL.
My version:
$paths = array(
0 => '/',
1 => '/404/',
2 => '/abstracts/',
3 => '/abstracts/edit/',
4 => '/abstracts/review/',
5 => '/abstracts/view/',
6 => '/admin/',
7 => '/admin/ads/',
// ....
);
$tree = array();
foreach($paths as $path){
$tmp = &$tree;
$pathParts = explode('/', rtrim($path, '/'));
foreach($pathParts as $pathPart){
if(!array_key_exists($pathPart, $tmp)){
$tmp[$pathPart] = array();
}
$tmp = &$tmp[$pathPart];
}
}
echo json_encode($tree, JSON_PRETTY_PRINT);
https://ideone.com/So1HLm
http://ideone.com/S9pWw
$arr = array(
'/',
'/404/',
'/abstracts/',
'/abstracts/edit/',
'/abstracts/review/',
'/abstracts/view/',
'/admin/',
'/admin/ads/',
'/admin/ads/clickcounter/',
'/admin/ads/delete/',
'/admin/ads/edit/',
'/admin/ads/list/',
'/admin/ads/new/',
'/admin/ads/sponsordelete/',
'/admin/ads/sponsoredit/',
'/admin/ads/sponsornew/',
'/admin/ads/stats/',
'/admin/boilerplates/',
'/admin/boilerplates/deleteboiler/',
'/admin/boilerplates/editboiler/',
'/admin/boilerplates/newboilerplate/',
'/admin/calendar/event/add/',
'/admin/calendar/event/copy/');
$result = array();
foreach ($arr as $node) {
$result = magic($node, $result);
}
var_dump($result);
function magic($node, $tree)
{
$path = explode('/', rtrim($node, '/'));
$original =& $tree;
foreach ($path as $node) {
if (!array_key_exists($node, $tree)) {
$tree[$node] = array();
}
if ($node) {
$tree =& $tree[$node];
}
}
return $original;
}
<?php
$old_array = array("/", "/404/", "/abstracts/", "/abstracts/edit/", "/abstracts/review/", "/rrl/");
$new_array = array();
foreach($old_array as $woot) {
$segments = explode('/', $woot);
$current = &$new_array;
for($i=1; $i<sizeof($segments); $i++) {
if(!isset($current[$segments[$i]])){
$current[$segments[$i]] = array();
}
$current = &$current[$segments[$i]];
}
}
print_r($new_array);
?>
You might consider converting your text to a JSON string, then using json_decode() to generate the structure.
Array
(
[00000000017] => Array
(
[00000000018] => Array
(
[00000000035] => I-0SAYHADW4JJA
[00000000038] => I-RF10EHE25KY0
[00000000039] => I-8MG3B1GT406F
)
[00000000019] => I-7GM4G5N3SDJL
)
[00000000025] => Array
(
[00000000011] => I-HT34P06WNMGJ
[00000000029] => I-U5KKT1H8J39W
)
[00000000040] => I-GX43V2WP9KPD
[00000000048] => I-XM526USFJAH9
[00000000052] => I-M414RK3H987U
[00000000055] => I-GABD4G13WHX7
)
I have the above array and i want to create a treeview display..
any recommendation ?
I guess i have to elaborate furthe on my question..
I want to store those array according to the level of array..
Example , I want something look like this :
[level_1]=> 00000000017,00000000025,00000000040, 00000000048, 00000000052
[level_2]=> 00000000018,00000000019, 00000000011, 00000000029
[level_3]=> 00000000035, 00000000038, 00000000039
You want a modified breadth-first search. This has the correct results for your sample structure:
<?php
function BFTraverse(&$tree = NULL, $depth = 0)
{
if (empty($tree))
return FALSE;
$keys = array_keys($tree);
$struct["lvl_$depth"] = $keys;
foreach ($keys as $key)
{
if (is_array($tree[$key]))
{
$struct = array_merge_recursive($struct, BFTraverse($tree[$key], $depth + 1));
}
}
return $struct;
}
$data = array
('00000000017' => array
(
'00000000018' => array
(
'00000000035' => 'I-0SAYHADW4JJA',
'00000000038' => 'I-RF10EHE25KY0',
'00000000039' => 'I-8MG3B1GT406F'
),
'00000000019' => 'I-7GM4G5N3SDJL'
),
'00000000025' => array
(
'00000000011' => 'I-HT34P06WNMGJ',
'00000000029' => 'I-U5KKT1H8J39W'
),
'00000000040' => 'I-GX43V2WP9KPD',
'00000000048' => 'I-XM526USFJAH9',
'00000000052' => 'I-M414RK3H987U',
'00000000055' => 'I-GABD4G13WHX7'
);
$var = BFTraverse($data);
$i = 0;
foreach ($var as $level)
echo "Level " . ++$i . ': ' . implode(', ', $level) . "\n";
?>
The output is:
Level 1: 00000000017, 00000000025, 00000000040, 00000000048, 00000000052, 00000000055
Level 2: 00000000018, 00000000019, 00000000011, 00000000029
Level 3: 00000000035, 00000000038, 00000000039
edit: Modified in the sense that you want the keys and not the node values.
I ran into the same problem recently and this article by Kevin van Zonneveld helped me out.
Basically you have to use a recursive function. Check out the article, it's what you need!