Home > Software design >  Getting a list of sub-directories form an array of paths based on current path
Getting a list of sub-directories form an array of paths based on current path

Time:12-13

I want to get an array of immediate sub-directories based on the current path from a given list of paths. Here is an example list of paths and the desired result:

$dirs = [
    '/chris',
    '/chris/usa',
    '/david',
    '/',
    '/chris/canada/2022',
    '/david/uk',
];
$current_path = "/chris";

// Required result:
$sub_dirs = ['/usa', '/canada'];

The current path could be anything from / to the last sub directory, in which case I would end up with an empty $sub_dirs.

My best attempt at it is this:

$dirs = [
    '/chris',
    '/chris/usa',
    '/david',
    '/',
    '/chris/canada/2022',
    '/david/uk',
];

$sub_dirs = [];

$current_path = "/";

foreach($dirs as $dir){
    if(strstr($dir, $current_path)){
        $sub_dirs[] = str_replace($current_path, '', $dir);
    }
}

The above fails at two things. If the $current_path = "/" then the returned array is just paths without slashes since I strip them out with strstr() and if the $current_path = "/chris" then I still get the sub-directories.

How should I correctly solve it? Thank you!

CodePudding user response:

  • You can search for /chris/ to get only sub directories.
  • To get only the first directory of matching paths, you could use explode(), and get the first.
  • As of PHP8, you can use str_starts_with()

$dirs = ['/chris', '/chris/usa', '/david', '/', '/chris/canada/2022', '/david/uk'];

$current_path = "/";

$sub_dirs = [];
foreach($dirs as $dir) 
{
    // Ensure $dir starts with path   '/' (sub dir of $current_path)
    if (!str_starts_with($dir, $current_path)) {
        continue;
    }
    
    // Get the relative path from $current_path
    $relPath = substr($dir, strlen($current_path));
    
    // Get the first dir of this path
    [$dir2] = explode('/', ltrim($relPath,'/'));
    
    // skip same directory
    if (empty($dir2)) {
        continue;
    }
    
    // Store and add removed slash
    $sub_dirs[] = '/' . $dir2;
}

var_export(array_unique($sub_dirs));

Output

array (
  0 => '/usa',
  1 => '/canada',
)

CodePudding user response:

I like the approach of creating a tree out of the paths (adapted from this answer). Then it's easier to get the children based on a path.


function buildTree($paths)
{
    // Create a hierarchy where keys are the labels
    $rootChildren = [];
    foreach ($paths as $path) {
        $branch = explode("/", $path);
        $children = &$rootChildren;
        foreach ($branch as $label) {
            if ($label) {
                if (!isset($children[$label])) {
                    $children[$label] = [];
                }
                $children = &$children[$label];
            }
        }
    }
    // Create target structure from that hierarchy
    function recur($children)
    {
        $result = [];
        foreach ($children as $label => $grandchildren) {
            $result[$label] = recur($grandchildren);
        }
        return $result;
    }
    return recur($rootChildren);
}


function findChildren($tree, $path)
{
    $branch = explode("/", $path);
    $pointer = $tree;
    foreach ($branch as $label) {
        if ($label) {
            $pointer = $pointer[$label];
        }
    }
    return array_keys($pointer);
}

$dirs = [
    '/chris',
    '/chris/usa',
    '/david',
    '/',
    '/chris/canada/2022',
    '/david/uk',
];


$tree = buildTree($dirs);
print_r($tree);

$current_path = "/chris";
print_r(findChildren($tree, $current_path));

Output:

Array
(
    [chris] => Array
        (
            [usa] => Array
                (
                )

            [canada] => Array
                (
                    [2022] => Array
                        (
                        )

                )

        )

    [david] => Array
        (
            [uk] => Array
                (
                )

        )

)
Array
(
    [0] => usa
    [1] => canada
)

CodePudding user response:

I feel like I have a bit shorter answer than the previous ones, but not sure if it's the most efficient one though.

$dirs = [
  '/chris',
  '/chris/usa',
  '/david',
  '/',
  '/chris/canada/2022',
  '/david/uk',
];
$current_path = "/chris";

$sub_dirs = array_map(function($s) { return  '/'.explode("/", $s)[2]; },array_filter($dirs, function($dir) use ($current_path) {
    return substr($dir, 0, strlen($current_path)   1) === $current_path . '/';
}));

print_r($sub_dirs); // Output: Array ( [1] => /usa [4] => /canada )
  •  Tags:  
  • php
  • Related