13

I have this string:

var/log/file.log

I eventually want to end up with an array looking like this:

Array => [
    '1' => 'var',
    '2' => 'var/log',
    '3' => 'var/log/file.log'
]

I currently have this:

<?php
    $string = 'var/log/file.log';
    $array = explode('/', $string);
    $output = [
        1 => $array[0],
        2 => $array[0]. '/' .$array[1],
        3 => $array[0]. '/' .$array[1]. '/' .$array[2]
    ];

    echo '<pre>'. print_r($output, 1) .'</pre>';

This feels really counter-intuitive and I'm not sure if there's already something built into PHP that can take care of this.

How do I build an array using appending previous value?

treyBake
  • 6,440
  • 6
  • 26
  • 57
  • You can solve this by writing a recursive function using explode('/', $string, 2) - let me know if you need help for that – mark Jan 25 '19 at 09:06
  • @mark I think I do - not sure I've used recursive functions before :) – treyBake Jan 25 '19 at 09:08
  • @treyBake If you want to create all this dirs step by step, just read about mkdir with recursiv flag – splash58 Jan 25 '19 at 09:17

6 Answers6

11
<?php
$string = 'var/log/some/other/directory/file.log';
$array = explode('/', $string);

$i = 0;
foreach ($array as $data) {
    $output[] = isset($output) ? $output[$i - 1] . '/' . $data : $data;
    $i++;
}


echo '<pre>';

print_r($output);

A simpler solution is above. You simple set your new array field to be a concatenation of your previous one from your new array and the current one from your foreach.

Output is:

Array
(
    [0] => var
    [1] => var/log
    [2] => var/log/some
    [3] => var/log/some/other
    [4] => var/log/some/other/directory
    [5] => var/log/some/other/directory/file.log
)
pr1nc3
  • 8,108
  • 3
  • 23
  • 36
  • damn, another fast benchmark - 0.022642135620117, these answers are all really good, feels like Sophie's choice at the minute xD – treyBake Jan 25 '19 at 09:49
  • That's the best!!! You have plenty of answers to choose to solve your issue :) I used the example of the long path you gave just to be sure it's working fine and smooth. – pr1nc3 Jan 25 '19 at 09:50
  • Indeed haha I guess overtime to, people can come and choose their preference - this is the SO I love :D – treyBake Jan 25 '19 at 09:52
5

This solution takes the approach of starting with your input path, and then removing a path one by one, adding the remaining input to an array at each step. Then, we reverse the array as a final step to generate the output you want.

$input = "var/log/file.log";
$array = [];
while (preg_match("/\//i", $input)) {
    array_push($array, $input);
    $input = preg_replace("/\/[^\/]+$/", "", $input);
    echo $input;
}
array_push($array, $input);
$array = array_reverse($array);
print_r($array);

Array
(
    [0] => var
    [1] => var/log
    [2] => var/log/file.log
)

The above call to preg_replace strips off the final path of the input string, including the forward slash. This is repeated until there is only one final path component left. Then, we add that last component to the same array.

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • I like the use of preg_* and array_* here - feels very pro haha – treyBake Jan 25 '19 at 09:20
  • 1
    after doing some benchmark tests between all 3 answers, this placed second 0.0063381195068359 - but choosing this answer over the others for the learning of the array_* and preg_* functions, I feel like this overall benefits the question – treyBake Jan 25 '19 at 09:27
  • The `i` flag is not helpful. Rather than calling `preg_match()` at the start of each loop, you might use a `do while()` and check `preg_replace()`'s count reference value. – mickmackusa Aug 05 '22 at 08:44
4

You could do something like this with a foreach

<?php
$string = 'var/log/file.log';
$array = explode('/', $string);

$last = '';
$output = array();
foreach ($array as $key => $value) {
    $result = $last.$value;
    $output[$key] = $result;
    $last = $result.'/';
}

echo '<pre>'. print_r($output, 1) .'</pre>';
Oliver Nybo
  • 560
  • 1
  • 6
  • 24
  • It definitely works! I'll hang on for maybe <= 2 hours and if nothing else comes along, will accept this one! :) – treyBake Jan 25 '19 at 09:15
  • 1
    after doing some benchmark tests between all 3 answers, this placed first! But I prefer Tim's answer, mainly for learning purposes of array_* and preg_* functions. If I could accept multiple answers, I would! For your curisoity, benchmark result for this answer was 0.0032050609588623 – treyBake Jan 25 '19 at 09:29
4

You can get parent directory in a loop and add it to output variable. For example with help the following algorithm:

$path = 'var/log/file.log';
$output = [];

$pos = strlen($path);
while ($pos !== false) {
    $path = substr($path, 0, $pos);
    array_unshift($output, $path);
    $pos = strrpos($path, DIRECTORY_SEPARATOR);
}

or with dirname() function

$path = 'var/log/file.log';
$output = [];

do {
  array_unshift($output, $path);
  $path = dirname($path);
} while ($path !== '.');

Also, you can work with $path string as an array of chars and find directory separator in it:

$path = 'var/log/file.log';
$output = [];

$tmp = '';
$len = strrpos($path, DIRECTORY_SEPARATOR); // you can use strlen instead of strrpos,
                                            // but it'll look over filename also
for ($i = 0; $i < $len; $i++) {        
    if ($path[$i] === DIRECTORY_SEPARATOR) {
        $output[] = $tmp;
    }
    $tmp .= $path[$i];
}
$output[] = $path;

but keep in mind you couldn't use this way if $path string has multibyte encoding

The result of all methods will be:

Array (
     [0] => var
     [1] => var/log
     [2] => var/log/file.log 
)
Maksym Fedorov
  • 6,383
  • 2
  • 11
  • 31
  • I quite like this one, very robust! – treyBake Jan 25 '19 at 09:20
  • after doing some benchmark tests between all 3 answers, this placed third - though I like the minimislism of this answer, and if I could accept multiple answers, I would! For your curisoity, benchmark result for this answer was 0.0084600448608398 – treyBake Jan 25 '19 at 09:27
  • your alternative answer got 0.0058770179748535 - making it the second fastest! That's actually pretty damn good haha I'll make a note of the function use :) – treyBake Jan 25 '19 at 09:39
  • Upvoted because of the `dirname` version. I've tried to make my own answer and ended up writting your `dirname` version. Except my version added everything to the array and then returned it reversed (https://ideone.com/ifMJM4). – Ismael Miguel Jan 25 '19 at 18:08
3

Because I can't help myself, I benchmarked all these answers. @Yoshi's (deleted, but you can see the code below) answer came a fairly clear first, followed by @OliverNybo (about 15% slower), @pr1nc3 (about 35% slower), a gap to mine and @MaximFedorov's first and second answer (about 55-75% slower), then another gap to @TimBiegeleisen and finally to @MaximFedorov's last answer (which didn't actually return the correct result). Here are the results for 100,000 iterations (times in seconds):

enter image description here

Here's the testing code. Note I've removed a call to array_reverse where it was used as it doesn't do anything other than change the order of output.

<!DOCTYPE html>
<html>
<head>
    <style type="text/css">
        table {
            border-collapse: collapse;align-content:
        }
        td, th {
            border: 1px solid black;
            padding: 5px;
        }
    </style>
</head>
<body>
<pre>
<?php
$string = 'var/log/some/other/directory/file.log';
$elapsed = array();
foreach (array('TimBiegeleisen', 'pr1nc3', 'OliverNybo', 'MaximFedorov1', 'MaximFedorov2', 'MaximFedorov3', 'Nick') as $func) {
    $start = explode(' ', microtime());
    for ($i = 0; $i < 100000; $i++) $func($string);
    $elapsed[$func] = elapsed_time($start);
}
asort($elapsed);
$fastest = min($elapsed);

echo "<table><tr><th>Function</th><th>Elapsed Time</th><th>Delta</tr>";
foreach ($elapsed as $key => $value) {
    echo "<td>$key</td><td>$value</td>";
    echo "<td>" . sprintf("%.0f%%", ($value - $fastest) / $fastest * 100) . "</td></tr>";
}
echo "</table>\n";

function TimBiegeleisen($input) {
    $array = [];
    while (preg_match("/\//i", $input)) {
        array_push($array, $input);
        $input = preg_replace("/\/[^\/]+$/", "", $input);
    }
    array_push($array, $input);
    return $array;
//  return array_reverse($array);   
}

function pr1nc3($string) {
    $array = explode('/', $string);

    $i = 0;
    foreach ($array as $data) {
        $output[] = isset($output) ? $output[$i - 1] . '/' . $data : $data;
        $i++;
    }
    return $output;
}

function OliverNybo($string) {
    $array = explode('/', $string);

    $last = '';
    $output = array();
    foreach ($array as $key => $value) {
        $result = $last.$value;
        $output[$key] = $result;
        $last = $result.'/';
    }   
    return $output;
}

function MaximFedorov1($path) {
    $output = [];

    $pos = strlen($path);
    while ($pos !== false) {
        $path = substr($path, 0, $pos);
        array_unshift($output, $path);
        $pos = strrpos($path, '/');
    }
    return $output;
}

function MaximFedorov2($path) {
    $output = [];

    do {
      array_unshift($output, $path);
      $path = dirname($path);
    } while ($path !== '.');
    return $output;
}

function MaximFedorov3($path) {
    $output = [];
    $tmp = '';
    $len = strrpos($path, '/'); // you can use strlen instead of strrpos,
                                                // but it'll look over filename also
    for ($i = 0; $i < $len; $i++) {        
        if ($path[$i] === '/') {
            $output[] = $tmp;
        }
        $tmp .= $path[$i];
    }
    $output[] = $path;
    return $output;
}

function Nick($string) {
    $array = explode('/', $string);
    for ($c = count($array); $c > 0; ) {
        $output[--$c] = implode('/', $array);
        array_pop($array);
    }
    return $output;
//  return array_reverse($output)
}

function Yoshi($input) {
    $output = explode('/', $input);

    for ($i = 1, $lim = \count($output); $i < $lim; $i++) {
        $output[$i] = $output[$i - 1] . '/' . $output[$i];
    }
    return $output;
}

function elapsed_time(array $start) {
    $now = explode(' ', microtime());
    $deltasec = $now[1] - $start[1];
    $deltamsec = (float)$now[0] - (float)$start[0];
    return $deltasec + $deltamsec;
}


?>
</pre>
</body>
</html>
Nick
  • 138,499
  • 22
  • 57
  • 95
2

There are a lot of good answers here already but here's another slightly different way of doing this:

$string = 'var/log/some/other/directory/file.log';
$array = explode('/', $string);
for ($c = count($array); $c > 0; ) {
    $output[--$c] = implode('/', $array);
    array_pop($array);
}
for ($i = 0; $i < count($output); $i++) {
    echo "$output[$i]\n";
}

Output:

var 
var/log 
var/log/some 
var/log/some/other 
var/log/some/other/directory 
var/log/some/other/directory/file.log

Demo on 3v4l.org

Nick
  • 138,499
  • 22
  • 57
  • 95