Split String Into Array and Append Prev Value [duplicate]
Asked Answered
M

6

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?

Molar answered 25/1, 2019 at 9:0 Comment(3)
You can solve this by writing a recursive function using explode('/', $string, 2) - let me know if you need help for thatPresentment
@Presentment I think I do - not sure I've used recursive functions before :)Molar
@Molar If you want to create all this dirs step by step, just read about mkdir with recursiv flagChekhov
S
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.

Subsidence answered 25/1, 2019 at 9:16 Comment(2)
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 questionMolar
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.Miyamoto
C
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
)
Cortezcortical answered 25/1, 2019 at 9:46 Comment(3)
damn, another fast benchmark - 0.022642135620117, these answers are all really good, feels like Sophie's choice at the minute xDMolar
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.Cortezcortical
Indeed haha I guess overtime to, people can come and choose their preference - this is the SO I love :DMolar
S
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.

Subsidence answered 25/1, 2019 at 9:16 Comment(2)
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 questionMolar
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.Miyamoto
A
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>';
Amphibrach answered 25/1, 2019 at 9:9 Comment(2)
It definitely works! I'll hang on for maybe <= 2 hours and if nothing else comes along, will accept this one! :)Molar
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.0032050609588623Molar
F
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 
)
Fennessy answered 25/1, 2019 at 9:16 Comment(3)
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.0084600448608398Molar
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 :)Molar
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 (ideone.com/ifMJM4).Transpicuous
F
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>
Fleurette answered 3/2, 2019 at 4:24 Comment(0)
F
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

Fleurette answered 25/1, 2019 at 20:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.