Convert single array to multidimensional array in php - php

If a problem i try to solve now some hours, but simply cant find a solution.
If a single array of paths
$singleArray = array(
'/Web',
'/Web/Test1',
'/Web/Test2',
'/Web/Test2/Subfolder',
'/Web/Test3',
'/Public'
);
From that array i want to create a mulitdimensional array, which keeps the keys but put subfolders in the correct parent folders. Later i want to loop over the new array to create a folder tree (but thats not a problem)
The new array should look like this:
$multiArray = array(
'/Web'=>array(
'/Web/Test1'=>array(),
'/Web/Test2'=>array(
'/Web/Test2/Subfolder'=>array()
),
'/Web/Test3'=>array()
),
'/Public'=>array()
);

The code below will make the array you want. The key to solve your problem is to create a reference to the array every iteration.
<?php
$singleArray = array(
'/Web',
'/Web/Test1',
'/Web/Test2',
'/Web/Test2/Subfolder',
'/Web/Test3',
'/Public'
);
$multiArray = array();
foreach ($singleArray as $path) {
$parts = explode('/', trim($path, '/'));
$section = &$multiArray;
$sectionName = '';
foreach ($parts as $part) {
$sectionName .= '/' . $part;
if (array_key_exists($sectionName, $section) === false) {
$section[$sectionName] = array();
}
$section = &$section[$sectionName];
}
}

Got this working! Great challenge!
First I sort the array by number of folders, so that the first to be processed are those with fewest folders (in the root).
Then the function iterates through each of the array items and each folder in that item, comparing it to the existing items in the array and, if it exists, placing it inside of that item as a multidimensional array.
This will work for up to two subfolders - /root/sub1/sub2 - but it's quite straightforward so simple to add functionality for deeper use.
This sample code also prints out the before/after arrays:
$singleArray = array(
'/Web',
'/Web/Test1',
'/Web/Test2',
'/Web/Test2/Subfolder',
'/Web/Test3',
'/Public'
);
echo "<pre>";
print_r($singleArray);
$multiArray = array();
//first sort array by how many folders there are so that root folders are processed first
usort($singleArray, function($a, $b) {
$a_folders = explode("/", $a);
$b_folders = explode("/", $b);
$a_num = count($a_folders); //number of folders in first
$b_num = count($b_folders); //number of folders in second
if($a_num > $b_num) return -1;
elseif($a_num < $b_num) return 1;
else return 0;
});
//foreach in array
foreach($singleArray as $item){
//get names of folders
$folders = explode("/", $item);
//if the first folder exists
if(in_array($folders[0], $multiArray)){
$key1 = array_search($folders[0], $multiArray);
//repeat for subfolder #1
if(in_array($folders[1], $multiArray[$key1])){
$key2 = array_search($folders[1], $multiArray[$key1]);
//repeat for subfolder #2
if(in_array($folders[2], $multiArray[$key1][$key2])){
$key3 = array_search($folders[2], $multiArray[$key1][$key2]);
array_push($multiArray[$key1][$key2][$key3], $item);
} else array_push($multiArray[$key1][$key2], $item);
} else array_push($multiArray[$key1], $item);
} else array_push($multiArray, $item);
}
//reverse the array so that it looks nice
$multiArray = array_reverse($multiArray);
print_r($multiArray);
This will output:
Array
(
[0] => /Web
[1] => /Web/Test1
[2] => /Web/Test2
[3] => /Web/Test2/Subfolder
[4] => /Web/Test3
[5] => /Public
)
Array
(
[0] => /Web
[1] => /Public
[2] => /Web/Test1
[3] => /Web/Test2
[4] => /Web/Test3
[5] => /Web/Test2/Subfolder
)

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

PHP multidimensional array not giving output

I've tried to display this information tons of times, i've looked all over stackoverflow and just can't find an answer, this isn't a duplicate question, none of the solutions on here work. I've a json array which is stored as a string in a database, when it's taken from the database it's put into an array using json_decode and looks like this
Array
(
[0] => Array
(
[0] => Array
(
)
[1] => Array
(
[CanViewAdminCP] => Array
(
[Type] => System
[Description] => Grants user access to view specific page
[Colour] => blue
)
)
)
)
However, when i try to loop through this, it just returns nothing, I've tried looping using keys, i've tried foreach loops, nothing is returning the values, I'm looking to get the Array key so "CanViewAdminCP" and then the values inside that key such as "Type" and "Description".
Please can anybody help? thankyou.
Use a recursive function to search for the target key CanViewAdminCP recursively, as follows:
function find_value_by_key($haystack, $target_key)
{
$return = false;
foreach ($haystack as $key => $value)
{
if ($key === $target_key) {
return $value;
}
if (is_array($value)) {
$return = find_value_by_key($value, $target_key);
}
}
return $return;
}
Example:
print_r(find_value_by_key($data, 'CanViewAdminCP'));
Array
(
[Type] => System
[Description] => Grants user access to view specific page
[Colour] => blue
)
Visit this link to test it.
You have a 4 level multidimensional array (an array containing an array containing an array containing an array), so you will need four nested loops if you want to iterate over all keys/values.
This will output "System" directly:
<?php echo $myArray[0][1]['CanViewAdminCP']['Type']; ?>
[0] fetches the first entry of the top level array
[1] fetches the second entry of that array
['CanViewAdminCP'] fetches that keyed value of the third level array
['Type'] then fetches that keyed value of the fourth level array
Try this nested loop to understand how nested arrays work:
foreach($myArray as $k1=>$v1){
echo "Key level 1: ".$k1."\n";
foreach($v1 as $k2=>$v2){
echo "Key level 2: ".$k2."\n";
foreach($v2 as $k3=>$v3){
echo "Key level 3: ".$k3."\n";
}
}
}
Please consider following code which will not continue after finding the first occurrence of the key, unlike in Tommassos answer.
<?php
$yourArray =
array(
array(
array(),
array(
'CanViewAdminCP' => array(
'Type' => 'System',
'Description' => 'Grants user access to view specific page',
'Colour' => 'blue'
)
),
array(),
array(),
array()
)
);
$total_cycles = 0;
$count = 0;
$found = 0;
function searchKeyInMultiArray($array, $key) {
global $count, $found, $total_cycles;
$total_cycles++;
$count++;
if( isset($array[$key]) ) {
$found = $count;
return $array[$key];
} else {
foreach($array as $elem) {
if(is_array($elem))
$return = searchKeyInMultiArray($elem, $key);
if(!is_null($return)) break;
}
}
$count--;
return $return;
}
$myDesiredArray = searchKeyInMultiArray($yourArray, 'CanViewAdminCP');
print_r($myDesiredArray);
echo "<br>found in depth ".$found." and traversed ".$total_cycles." arrays";
?>

Recursively crawl data folder and create multidimensional array

So I have the following situation. In my project folder I got a 'data' folder that contains .json files. These .json files also are structured in nested folders.
Something like:
/data
/content
/data1.json
/data2.json
/project
/data3.json
I'd like to create a function that recursively crawls through the data folder and stores all .json files in one multidimensional array, which makes it relatively easy to add static data for use for my project. So the expected result should be:
$data = array(
'content' => array(
'data1' => <data-from-data1.json>,
'data2' => <data-from-data2.json>
),
'project' => array(
'data3' => <data-from-data3.json>
)
);
UPDATE
I have tried the following, but this only returns the first level:
$data = array();
$directoryArray = scandir('./data');
foreach($directoryArray as $key => $value) {
$data[$key] = $value;
}
Is there a neat way to achieve this?
You should use RecursiveIteratorIterator. Skip some directories like . and .. . After this script loop other subdirectories.
//just to remove extension filename
function removeExtension($filename){
return preg_replace('/\\.[^.\\s]{3,4}$/', '', $filename);
}
$startpath= 'data';
$ritit = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($startpath), RecursiveIteratorIterator::CHILD_FIRST);
$result = [];
foreach ($ritit as $splFileInfo) {
if ($splFileInfo->getFilename() == '.') continue;
if ($splFileInfo->getFilename() == '..') continue;
if ($splFileInfo->isDir()){
$path = [removeExtension($splFileInfo->getFilename()) => []];
}else{
$path = [removeExtension($splFileInfo->getFilename()) => json_decode(file_get_contents($splFileInfo->getPathname(), $splFileInfo->getFilename()))];
}
for ($depth = $ritit->getDepth() - 1; $depth >= 0; $depth--) {
$path = [$ritit->getSubIterator($depth)->current()->getFilename() => $path];
}
$result = array_merge_recursive($result, $path);
}
print_r($result);
My json files contain:
data1.json: {"foo": "foo"}
data2.json: {"bar": "bar"}
data3.json: {"foobar": "foobar"}
The result is:
Array
(
[content] => Array
(
[data1] => stdClass Object
(
[foo] => foo
)
[data2] => stdClass Object
(
[bar] => bar
)
)
[project] => Array
(
[data3] => stdClass Object
(
[foobar] => foobar
)
)
)
You do not really have to use RecursiveIteratorIterator. As a programmer you should always know how to deal with recursive data structures, may it be an xml content, a folder tree or else. You may write a recursive function to handle such tasks.
Recursive functions are functions which call themselves to process through data with multiple layers or dimensions.
For example, scanFolder function below is designed to process contents of a directory, and it calls itself when it is encountered with a sub-directory.
function scanFolder($path)
{
echo "scanning dir: '$path'";
$contents = array_diff(scandir($path), ['.', '..']);
$result = [];
foreach ($contents as $item) {
$fullPath = $path . DIRECTORY_SEPARATOR . $item;
echo "processing '$fullPath'";
// process folder
if (is_dir($fullPath)) {
// process folder contents
$result[$item] = scanFolder($fullPath);
} else {
// for this specific program, you should perform a check here to see if the file is a json
// collect the result
$result[$item] = json_decode(file_get_contents($fullPath));
}
}
return $result;
}
IMO, this is a cleaner and more expressive way to accomplish this task and I wonder what others have to say about this statement.
I think that you can use RecursiveDirectoryIterator, there is an documentation about this class.

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;

Make a nested array of subdirectories using RecursiveDirectoryIterator

I have a following directory structure
test
directory_in_test
directory_in_directory_in_test
directory2_in_test
directory_in_directory2_in_test
abc.php
index.php
I am trying to make a function that will give a multidimensional array of sub-directories. Required output something like :
[directories] => Array(
[test] => Array(
[directory_in_test] => Array(
[directory_in_directory_in_test] => null
)
[directory2_in_test] => Array(
[directory_in_directory2_in_test] => null
)
)
)
I have tried to used RecursiveIteratorIterator with RecursiveDirectoryIterator but it give a one-level array of directories and files which is far from my requirement. Here is the code and result i have
code
<?php
public function findDirectories($path = '', $like = '')
{
$path = (is_dir($path)) ? $path : getcwd();
$directories = array();
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
foreach ($iterator as $directory) {
if($directory->isDir())
$directories[] = $directory->getPathName();
}
return $directories;
}
Result on printing $directories
Array
(
[0] => D:\xampp\htdocs\raheelwp\file-resolver\tests\.
[1] => D:\xampp\htdocs\raheelwp\file-resolver\tests\..
[2] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory2_in_test\.
[3] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory2_in_test\..
[4] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory2_in_test\directory_in_directory2_in_test\.
[5] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory2_in_test\directory_in_directory2_in_test\..
[6] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory_in_test\.
[7] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory_in_test\..
[8] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory_in_test\direcotry_in_directory_in_test\.
[9] => D:\xampp\htdocs\raheelwp\file-resolver\tests\directory_in_test\direcotry_in_directory_in_test\..
)
<?php
$it = new RecursiveDirectoryIterator(".", RecursiveDirectoryIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($it);
$files = new RecursiveArrayIterator(array());
foreach ($it as $fi) {
$it = $files;
$dirs = explode('/', $fi->getPath());
foreach ($dirs as $path) {
if (isset($it[$path])) {
$it = $it[$path];
} else {
$it[$path] = new RecursiveArrayIterator();
}
}
$it[$fi->getFileName()] = $fi->getFileName();
}
$a = array();
createArray($a, $files);
print_r($a);
function createArray(&$a, $it) {
foreach ($it as $k => $tmp) {
if (is_string($tmp)) {
$a[] = $tmp;
} else {
$a[$k] = array();
createArray($a[$k], $tmp);
}
}
}
The code is fairly simple, and split in two parts even though it could easily be created in just one part. The first part will split the directories into separate RecursiveArrayIterators, so you keep the "iterator" capabilities to do all kind of other stuff with it. This is often useful when you are using the SPL iterators to begin with.
The second part, the createArray function basically uses an array reference to point to the "current" directory. Since it will be a multidimensional array, we do not have to worry about "where" in the array we actually are (it could be the 1st level, it might as well be the 100th level if your directory structure goes that deep). It just checks if the given element is a string, if so, it's a file, otherwise it's a directory so we recursively call the createArray again.
Might be easier solutions, but I reckon most of them uses a basic array-reference system nevertheless.

Categories