1

I need some advice concerning string manipulation. This is a possible string:

"/branches/projects/MyTool/MyTool/someOtherFolderOrFile"

What I need is an array with the following in it:

 ['/branches', '/branches/projects','/branches/projects/MyTool','/branches/projects/MyTool/MyTool']

So to clarify what this array should contain: All parent paths to the last file or folder. The last file or folder is excluded in the array.

I know I can split the string at "/" and then could iterate over the array, concatenate strings and delete the last entry from the array. This method I would call recursively until there is no element in the array left. The concatenated string would then be saved at every iteration to a new array which would hold the desired result at the end......

However I asked me how to do it very clean and simple and I am sure someone of you can come up with a superb solution :)

EDIT Here is my current solution I came up with just now:

var string = "/branches/projects/MyTool/MyTool/anotherFolderOrFile";
var helperArr = string.split("/");
var endArray = [];

getParentArray();
console.log(endArray);

function getParentArray() {
    if(helperArr.length == 1) {
        return;
    }
    else {
        var helperString = "";
        for(var i = 0;i<helperArr.length;i++) {
            helperString = helperString+helperArr[i];
            if(i!= helperArr.length -1) {
                helperString = helperString+"/";
            }
        }
        endArray.push(helperString);
        helperArr.length = helperArr.length-1;
        getParentArray();
    }
}
2
  • Is the URL always in the exact same format? Commented Feb 13, 2015 at 15:21
  • its more a file path then an URL but yes. It will always begin with a / and always end without a slash. Just the number of elements in the path can vary (like "/branches/test" or "/trunk/test2/anotherElement") Commented Feb 13, 2015 at 15:23

8 Answers 8

4

Here's a compact solution, but it may not be as clear as @somethinghere 's:

var path= '/branches/projects/MyTool/MyTool/someOtherFolderOrFile'.split('/'),
    paths= [];

path.shift();
while(path.length > 1) {
  paths.push((paths[paths.length-1] || '') + '/' + path.shift());
}

alert(paths.join('\r'));

Update

Thanks for the comments. Here's a more compact version, which doesn't require splitting or shifting:

var path= '/branches/projects/MyTool/MyTool/someOtherFolderOrFile',
    paths= [],
    i = 1;

while(i = path.indexOf('/',i)+1) {
  paths.push(path.substr(0,i));
}

alert(paths.join('\r'));

Update #2

And just for giggles, a third solution, which should be more legible than my others, but still a little slower than @somethinghere 's:

var path= '/branches/projects/MyTool/MyTool/someOtherFolderOrFile',
    paths= [],
    i;

for(var i = 1 ; i < path.length ; i++) {
  if(path[i]==='/') paths.push(path.substr(0,i));
}
 
alert(paths.join('\r'));

Sign up to request clarification or add additional context in comments.

10 Comments

Was thinking of something like this as well :-)
giving you an upvote because I like your answer too :)
There are actually a lot of good, but very different solutions in here . . .
Yeah I like this as well, but I do find it a tad less legible. Is there an advantage to using shift() (as in, would it win some resources compared to the looping solution I used or is the difference negligible?)
@somethinghere, I agree my first code is less legible than yours. I've added a new version, which avoids so much array manipulation. But the single equal sign in the while loop might be confusing to some.
|
2

This will do the trick:

/* First split the path at any folders separator ('/')*/
var splitPath = "/branches/projects/MyTool/MyTool/someOtherFolderOrFile".split("/");
/* Initialise a final array to save the paths. Store the first one in there! */
var endPaths = [splitPath[0] + "/"];
/* Check if there are multiple paths available */
if(splitPath.length >= 2){
    /* Run through each and append it to the last saved one, then add it to the results.*/
    for(var i = 1; i <= splitPath.length-1; i++){
        endPaths.push(endPaths[i-1] + splitPath[i] + "/")
    }
}

This will include / as a path, which is technically a path. If you don't want that, you can use this line instead at the initialisation to exclude any simple paths like that:

var endPaths = splitPath[0] != "" ? [splitPath[0] + "/"] : [];

If you do the above though, beware that your loop will throw an error as endPaths[i-1] will be undefined when your loop starts running, you will have to do a check inside your for loop:

/* You need to do -2 as you start at 1, but you only want to add paths from 2 upwards.*/
var prevPath = typeof endPaths[i-2] !== "undefined" ? endPaths[i-2] : "";
endPaths.push(prevPath + splitPath[i] + "/")

5 Comments

Thanks this looks much cleaner then the version I came up with. I'll go with this one.
Good :) I added a little bit as my suggestion for ignoring the initial / could throw an error.
well your edit is not really working for me. But I get the point and will adapt this one to suit my needs. Anyway you helped me with guiding me to the right direction.
Sorry, because you skip one step in the original Array you actually need to go back two steps, not one. I've amended it. Also, if you do the for loop up to splitPath.length-2 instead of splitPath.length-1 you exclude the last item as well, as you wanted.
@dehlen - This is actually the solution that I thought you were describing in the original question. LOL
1

And, for a little more variety, a recursive, RegExp-based solution:

function iterateMatch(testString, currPattern, results) {
    var regexTestPattern = new RegExp("^" + currPattern + "\/[^\/]+");
    results.push(testString.match(regexTestPattern)[0]);

    if (results[results.length - 1] !== testString) {
        iterateMatch(testString, results[results.length - 1], results)
    }
}

The function takes in:

  1. the test string,
  2. a pattern to "ignore" during the match
  3. the array to turn the results to

The "ignore" pattern will start out as a blank string, but, during every iteration, will contain the previously matched directory level. This string is used as the beginning of the matching pattern, which then adds a RegExp pattern to the end, containing a RegExp pattern to match a forward slash and one or more non-slash characters.

The match is captured in the results array and, if it does not equal the test string, is passed into the iterateMatch function again as the next "ignore" pattern (along with the original test string and results array), so that the match starts looking at the next level down.

When called like this, with your test string:

var sTestString = "/branches/projects/MyTool/MyTool/someOtherFolderOrFile";

var sCurrPattern = "";
var aResults = [];

iterateMatch(sTestString, sCurrPattern, aResults);

console.log(aResults);

. . . the result is:

 ["/branches",
  "/branches/projects",
  "/branches/projects/MyTool",
  "/branches/projects/MyTool/MyTool",
  "/branches/projects/MyTool/MyTool/someOtherFolderOrFile"]

1 Comment

Like the use of regex :)
1

This reduce solved my use-case (without a preceding /):

const string = "a/b/c/d";
const stringSplits = string.split('/');
const parentArray = stringSplits.reduce((acc, val, i) => {
  if (i === 0) return [val]
  acc.push( acc[i-1] + '/' + val )
  return acc
}, []);

// returns ["a", "a/b", "a/b/c", "a/b/c/d"] 

Or this one-liner if you're absolutely bonkers:

const parentArray = "a/b/c/d".split('/').reduce((acc, val, i) => i === 0 ? [val] : [].concat(acc, acc[i-1] + '/' + val ), []);

Comments

0

Not at all short and not at all elegant... A bit different approach...

function split_string_to_path( str ) {
  if (typeof str == "undefined")
    return false; // return, if no string

  split_path = str.split("/"); // splitted string

  result_arr = []; // result paths into this variable
  tmp_arr = []; // temporary, for join

  for( i = 0; i < split_path.length; i++ ) { // loop through all elements
    tmp_arr.push( split_path[i] ); // push element
    result_path = tmp_arr.join( "/" );

    if ( result_path.length > 0 )
      result_arr.push( tmp_arr.join( "/" ) ); // join tmp arr to path and add it to result_arr
  }

  return result_arr;
}

split_result = split_string_to_path( "/branches/projects/MyTool/MyTool/someOtherFolderOrFile" ); // run function

console.log( split_result ); // output on console

1 Comment

There are many imporvement to be made, For example, instead of if(typeof str === "undefined") you could simply use if(!str), as str will be falsy but can be checked against (as its defined in the function). Since the variable str is not used after splitting, you could rewrite the split() into str itself to avoid making anothing variable and thereby saving resources. Also, and its just a sidenote, but I think your function should be named split_path_to_string :)
0

Here's my take on it. Using a little jQuery to help us out makes for a nice concise function (even if it is a bit inefficient).

    function getParentArray(path) {
        // split up on the forward slashes
        var splitPath = path.split('/');
        // Don't need the last part of the path
        splitPath.pop();
        // put an ever growing slice of the original path into a new array
        return $.map(splitPath, function(value, i) {
            return '/' + splitPath.slice(1, i + 1).join('/');
        }).splice(1);
    }

    var samplePath = '/branches/projects/MyTool/MyTool/someOtherFolderOrFile';
    var parentArray = getParentArray(samplePath);

    alert(parentArray.join('\n'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Comments

0

Try

var str = "/branches/projects/MyTool/MyTool/someOtherFolderOrFile"
, d = "/"
, arr = str.split(d).filter(Boolean)
, res = arr.map(function(val, i) {
            return i < 1 ? d + val : d + arr.slice(0, i).join(d) + d + arr[i] 
  }).slice(0, arr.length - 1);

    var str = "/branches/projects/MyTool/MyTool/someOtherFolderOrFile"
    , d = "/"
    , arr = str.split(d).filter(Boolean)
    , res = arr.map(function(val, i) {
              return i < 1 ? d + val : d + arr.slice(0, i).join(d) + d + arr[i] 
      }).slice(0, arr.length - 1); 

document.querySelectorAll("pre")[0].innerText = JSON.stringify(res, null, 4)
<pre></pre>

Comments

0

Not particularly efficient but fairly readable.

path
  .split('/')
  .filter(s => !!s)
  .reduce(
    (acc, val, i) => acc.toSpliced(0, 0, acc[0] + (i ? '/' : '') + val),
    ['/'],
  )
  .toReversed();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.