PHP - Create Hierarchal Array - php

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.

Related

How to create hierarchy in array?

I know I'm not describing well my question, but I want to create "nested array" as you can see:
folder/ -> folder/file.txt, folder/folder2/ -> folder/folder2/file.txt, folder/folder2/folder3/ -> etc
but instead, I get:
E:\wamp\www\index.php:31:
array (size=3)
'folder/' =>
array (size=1)
0 => string 'folder/file.txt' (length=15)
'folder/folder2/' =>
array (size=1)
0 => string 'folder/folder2/file.txt' (length=23)
'folder/folder2/folder3/' =>
array (size=1)
0 => string 'folder/folder2/folder3/file.txt' (length=31)
My code is:
$array = [
'folder/',
'folder/folder2/folder3/',
'folder/folder2/',
'folder/folder2/folder3/file.txt',
'folder/folder2/file.txt',
'folder/file.txt'
];
sort($array);
$array = array_flip($array);
function recursive_dir_nested($a) {
foreach ($a as $k => $v) {
if (preg_match("/\/$/", $k)) {
$a[$k] = [];
}
if (preg_match("/\/[^\/]+$/", $k)) {
$nk = preg_replace("/\/[^\/]+$/", "/", $k);
if (array_key_exists($nk, $a)) {
$a[$nk][] = $k;
unset($a[$k]);
} else {
recursive_dir_nested($a);
}
}
}
return $a;
}
I know I do something wrong, I'm not sure why... How can I solve this?
Not sure if using regex's is the best way to go. This builds on another answer - PHP - Make multi-dimensional associative array from a delimited string, but adds in the idea of using an array of entries. The one thing to note is that when adding new entries, if the element isn't currently an array, it turns it into an array so it can contain multiple entries ( the if ( !is_array($current) ) { part).
It uses each string and builds the folder hierarchy from that, saving the last part as the file name to be added specifically to the folder element...
$array = [
'folder/',
'folder/folder2/folder3/',
'folder/folder2/',
'folder/folder2/folder3/file.txt',
'folder/folder2/file.txt',
'folder/file.txt'
];
sort($array);
$output = [];
foreach ( $array as $entry ) {
$split = explode("/", $entry);
$current = &$output;
$file = array_pop($split);
foreach ( $split as $level ) {
if ( !isset($current[$level]) ){
if ( !is_array($current) ) {
$current = [ $current ];
}
$current[$level] = [];
}
$current = &$current[$level];
}
if ( !empty($file) ) {
$current = $file;
}
}
print_r($output);
This gives you...
Array
(
[folder] => Array
(
[0] => file.txt
[folder2] => Array
(
[0] => file.txt
[folder3] => file.txt
)
)
)
You can nest arrays in PHP. You might also want to use keys for the names of the directories:
$array = [
'folder' => [
'folder2' => [
'folder3' => [
'file.txt'
],
'file.txt'
],
'file.txt'
]
];
You could check each item with is_array() to see if it itself is array, then treat it as a string if it isn't.
See here for more info: php.net/manual/en/language.types.array.php

Array returning images from previous iteration

For some reach my array returns the data numerous times, for different arrays after filtering via a foreach in the second example the post only contains 1 unique link so the rest shouldn't exist at all, it should print simply the data from image_1 here's the PHP code.
$imagecounter = 1;
foreach ($html2->find('.post img') as $source) {
$link = $source->src;
if (strpos($link, 'https://www.example.com/wp-content/uploads/') !== false) {
$data['image_'.$imagecounter++.''] = $link;
}
}
And here's the array.
Array
(
[url] => https://www.example.com/something-with-data/
[featured_image] => https://www.example.com/wp-content/uploads/2018/11/something-1.jpg
[name] => Main Categories
[image_1] => https://www.example.com/wp-content/uploads/2018/11/something-11.jpg
[image_2] => https://www.example.com/wp-content/uploads/2018/10/something-1.jpg
[image_3] => https://www.example.com/wp-content/uploads/2018/10/something-1.png
[image_4] => https://www.example.com/wp-content/uploads/2018/10/something-2.jpg
[image_5] => https://www.example.com/wp-content/uploads/2018/11/something-3.jpg
[image_6] => https://www.example.com/wp-content/uploads/2018/11/something-4.jpg
[image_7] => https://www.example.com/wp-content/uploads/2018/11/something-5.jpg
[image_8] => https://www.example.com/wp-content/uploads/2018/11/something-6.jpg
[image_9] => https://www.example.com/wp-content/uploads/2018/11/something-7.jpg
[image_10] => https://www.example.com/wp-content/uploads/2018/11/something-8.jpg
[image_11] => https://www.example.com/wp-content/uploads/2018/11/something-9.jpg
)
Array
(
[url] => https://www.example.com/a-completely-different-post
[featured_image] => https://www.example.com/wp-content/uploads/2018/10/UmekRLrlwK8.jpg
[name] => Main Categories
[image_1] => https://www.example.com/wp-content/uploads/2018/10/UmekRLrlwK8.jpg (THIS IS FROM THE NEW ITERATION)
[image_2] => https://www.example.com/wp-content/uploads/2018/10/something-1.jpg
[image_3] => https://www.example.com/wp-content/uploads/2018/10/something-1.png
[image_4] => https://www.example.com/wp-content/uploads/2018/10/something-2.jpg
[image_5] => https://www.example.com/wp-content/uploads/2018/10/something-3.jpg
[image_6] => https://www.example.com/wp-content/uploads/2018/10/something-4.jpg
[image_7] => https://www.example.com/wp-content/uploads/2018/10/something-5.jpg
[image_8] => https://www.example.com/wp-content/uploads/2018/10/something-6.jpg
[image_9] => https://www.example.com/wp-content/uploads/2018/10/something-7.jpg
[image_10] => https://www.example.com/wp-content/uploads/2018/10/something-8.jpg
[image_11] => https://www.example.com/wp-content/uploads/2018/10/something-9.jpg
[image_12] => https://www.example.com/wp-content/uploads/2018/10/something-10.jpg
)
Start with an empty $data array.
$data = [];
$imagecounter = 1;
foreach ($html2->find('.post img') as $source) {
$link = $source->src;
if (strpos($link, 'https://www.example.com/wp-content/uploads/') !== false) {
$data['image_'.$imagecounter++.''] = $link;
}
}
If you need to store multiple sets of $data, then you can store each new $data batch in a parent array.
foreach ($htmls as $html2) {
$data = [];
$imagecounter = 1;
foreach ($html2->find('.post img') as $source) {
$link = $source->src;
if (strpos($link, 'https://www.example.com/wp-content/uploads/') !== false) {
$data['image_'.$imagecounter++.''] = $link;
}
}
$result[] = $data;
}

Reduce URL strings with no duplicates

I have an array that looks like the following...
$urls = array(
"http://www.google.com",
"http://www.google.com/maps",
"http://www.google.com/mail",
"https://drive.google.com",
"https://www.youtube.com",
"https://www.youtube.com/feed/subscriptions",
"https://www.facebook.com/me",
"https://www.facebook.com/me/friends"
);
I find this hard to explain but I want to break this array down to only show the reduced URLs with no duplicates, so it looks like this...
$urls = array(
"http://www.google.com",
"https://drive.google.com",
"https://www.youtube.com",
"https://www.facebook.com/me"
);
Notice the last URL in the second array still has it's path. This is because I want still want to show the lowest level paths
Based on #Tim's answer
foreach ($urls as &$url) {
$url_parts = parse_url($url);
$url = $url_parts["scheme"]."://".$url_parts["host"];
}
$urls = array_unique($urls);
Just sort the array in reverse order, and create an array indexed by host:
$urls = array(
"http://www.google.com",
"http://www.google.com/maps",
"http://www.google.com/mail",
"https://drive.google.com",
"https://www.youtube.com",
"https://www.youtube.com/feed/subscriptions",
"https://www.facebook.com/me",
"https://www.facebook.com/me/friends"
);
rsort($urls);
$return = [];
foreach($urls as $url) {
$host = parse_url($url, PHP_URL_HOST);
$return[$host] = $url;
}
$return = array_values($return); // To remove array keys, if desired.
The reverse-ordered urls array would be:
Array
(
[0] => https://www.youtube.com/feed/subscriptions
[1] => https://www.youtube.com
[2] => https://www.facebook.com/me/friends
[3] => https://www.facebook.com/me
[4] => https://drive.google.com
[5] => http://www.google.com/maps
[6] => http://www.google.com/mail
[7] => http://www.google.com
)
Since the last entry (per host name) in the sorted array is the one that you want, and it deliberately clobbers any existing array value, this would output:
Array
(
[www.youtube.com] => https://www.youtube.com
[www.facebook.com] => https://www.facebook.com/me
[drive.google.com] => https://drive.google.com
[www.google.com] => http://www.google.com
)
Try this:
$result = array();
array_push($result, $urls[0])
for($i=1; $i<count($urls); $i++)
{
$repeat = false;
foreach($result as $res)
{
if(strpos($urls[i], $res))
{
$repeat = true;
break;
}
}
if(!repeat)
array_push($result, $urls[i])
}
return $result;

Dynamically creating a multidimensional array based on paths

So I've got a list of paths, such as:
path/to/directory/file1
path/directory/file2
path2/dir/file3
path2/dir/file4
And I'd like to convert them into a multidimensional array like this:
array(
path => array(
to => array(
directory => array(
file1 => someValue
),
),
directory => array(
file2 => someValue
),
),
path2 => array(
dir => array(
file3 => someValue,
file4 => someValue
)
)
)
My first thought was to explode() the paths into segments and set up the array using a foreach loop, something like this:
$arr = array();
foreach ( $path as $p ) {
$segments = explode('/', $p);
$str = '';
foreach ( $segments as $s ) {
$str .= "[$s]";
}
$arr{$str} = $someValue;
}
But this doesn't work, and since the number of segments varies, I've kinda got stumped. Is there away to do this?
If somevalue can be an empty array:
<?php
$result = array();
$input = [
'path/to/directory/file1',
'path/directory/file2',
'path2/dir/file3',
'path2/dir/file4',
];
foreach( $input as $e ) {
nest( $result, explode('/', $e));
}
var_export($result);
function nest(array &$target, array $parts) {
if ( empty($parts) ) {
return;
}
else {
$e = array_shift($parts);
if ( !isset($target[$e]) ) {
$target[$e] = [];
}
nest($target[$e], $parts);
}
}
Here is the solution and a easy way
Just Reverse the whole exploded array and start creating array within a Array
$path[1] = "path/to/directory/file1";
$path[2] = "path/directory/file2";
$path[3] = "path2/dir/file3";
$path[4] = "path2/dir/file4";
$arr = array();
$b = array();
$k = 0;
foreach($path as $p) {
$c = 0;
$segments = explode('/', $p);
$reversed = array_reverse($segments);
foreach($reversed as $s) {
if ($c == 0) {
$g[$k] = array($s => "somevalue");
} else {
$g[$k] = array($s => $g[$k]);
}
$c++;
}
$k++;
}
var_dump($g);
Thanks so much VolkerK! Your answer didn't quite answer my question but it got me on the right track. Here's the version I ended up using to get it to work:
$result = array();
$input = [
'path/to/directory/file1' => 'someValue',
'path/directory/file2' => 'someValue',
'path2/dir/file3' => 'someValue',
'path2/dir/file4' => 'someValue',
];
foreach( $input as $e=>$val ) {
nest( $result, explode('/', $e), $val);
}
var_export($result);
function nest(array &$target, array $parts, $leafValue) {
$e = array_shift($parts);
if ( empty($parts) ) {
$target[$e] = $leafValue;
return;
}
if ( !isset($target[$e]) ) {
$target[$e] = [];
}
nest($target[$e], $parts, $leafValue);
}
I basically just added the somevalue as $leafValue and moved the base case around so that it would add the leafValue instead of a blank array at the end.
This results in:
Array
(
[path] => Array
(
[to] => Array
(
[directory] => Array
(
[file1] => someValue
)
)
[directory] => Array
(
[file2] => someValue
)
)
[path2] => Array
(
[dir] => Array
(
[file3] => someValue
[file4] => someValue
)
)
)
Thanks a lot!
It can be done without recursion
$path = array(
'path/to/directory/file1',
'path/directory/file2',
'path2/dir/file3',
'path2/dir/file4');
$arr = [];
$someValue = 'someValue';
foreach ( $path as $p ) {
$segments = explode('/', $p);
$str = '';
$p = &$arr;
foreach ( $segments as $s ) {
if (! isset($p[$s] ) ) $p[$s] = array();
$p = &$p[$s];
}
$p = $someValue;
}
print_r($arr);

Taking a string of period separated properties and converting it to a json object in php

I'm fairly sure I'm missing something blindingly obvious here but here it goes.
I am working on updating a search function in an application which was running a loop and doing a very large number of sql queries to get object / table relations to one large query that returns everything. However the only way I could think to return relations was period separated, what I am now wanting to do is take the flat array of keys and values and convert it into an associative array to then be jsonified with json_encode.
For example what I have is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
And what I want to turn it into is this...
array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus"=>array(
"Status"=>"Active"
),
"addressID"=>134,
"address"=>array(
"postcode"=>"XXX XXXX",
"street"=>"Some Street"
)
);
Now I'm sure this should be a fairly simple recursive loop but for the life of me this morning I can't figure it out.
Any help is greatly appreciated.
Regards
Graham.
Your function was part way there mike, though it had the problem that the top level value kept getting reset on each pass of the array so only the last period separated property made it in.
Please see updated version.
function parse_array($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
if(count($parts) > 1) {
$index = &$dst;
$i = 0;
$count = count($parts)-1;
foreach(array_slice($parts,0) as $part) {
if($i == $count) {
$index[$part] = $val;
} else {
if(!isset($index[$part])){
$index[$part] = array();
}
}
$index = &$index[$part];
$i++;
}
} else {
$dst[$parts[0]] = $val;
}
}
return $dst;
}
I am sure there is something more elegant, but quick and dirty:
$arr = array(
"ID"=>10,
"CompanyName"=>"Some Company",
"CompanyStatusID"=>2,
"CompanyStatus.Status"=>"Active",
"addressID"=>134,
"address.postcode"=>"XXX XXXX",
"address.street"=>"Some Street"
);
$narr = array();
foreach($arr as $key=>$val)
{
if (preg_match("~\.~", $key))
{
$parts = split("\.", $key);
$narr [$parts[0]][$parts[1]] = $val;
}
else $narr [$key] = $val;
}
$arr = array(
"ID" => 10,
"CompanyName" => "Some Company",
"CompanyStatusID" => 2,
"CompanyStatus.Status" => "Active",
"addressID" => 134,
"address.postcode" => "XXX XXXX",
"address.street" => "Some Street",
"1.2.3.4.5" => "Some nested value"
);
function parse_array ($src) {
$dst = array();
foreach($src as $key => $val) {
$parts = explode(".", $key);
$dst[$parts[0]] = $val;
if(count($parts) > 1) {
$index = &$dst[$parts[0]];
foreach(array_slice($parts, 1) as $part) {
$index = array($part => $val);
$index = &$index[$part];
}
}
}
return $dst;
}
print_r(parse_array($arr));
Outputs:
Array
(
[ID] => 10
[CompanyName] => Some Company
[CompanyStatusID] => 2
[CompanyStatus] => Array
(
[Status] => Active
)
[addressID] => 134
[address] => Array
(
[street] => Some Street
)
[1] => Array
(
[2] => Array
(
[3] => Array
(
[4] => Array
(
[5] => Some nested value
)
)
)
)
)

Categories