4

I have the following string which i want to use java regex to get the following results.

String s  = "/accounts/main/index/page.txt"
String[] result = {"/accounts/", "/accounts/main/", "/accounts/main/index/"};

That is, i would like to get the 'parent directory hierarchy' (This does not have to be a a directory structure).

NOTE: The string "s" is dynamically assigned, so it may be different levels of directory.

I have the following, but i am unsure of how to compile a regex that will return what i want. i know what i want can only return one result, the last entry in the array:

    Pattern p = Pattern.compile("^/.+/"); //how do i set up this regex to give me required results.
    String s = "/accounts/main/index/page.xhtml";
    Matcher m = p.matcher(s);
    while(m.find()){
      System.out.println(m.group());
    }

5 Answers 5

3

I wouldn't use a regex for this. What about something along the lines of

String[] split = s.split("/");

StringBuilder sb = new StringBuilder(s.lastIndexOf('/') + 1);  // our result
sb.append('/');  // initial "/"

for (int i = 0; i < split.length - 1; i++) {  // we don't care about the
    if (split[i].isEmpty())                   // last component
        continue;

    sb.append(split[i]);
    sb.append('/');
    System.out.println(sb);  // or add to an array/list/etc.
}
/accounts/
/accounts/main/
/accounts/main/index/
Sign up to request clarification or add additional context in comments.

5 Comments

I like this but I wish you hadn't used string buffer, it makes it so much longer, less clear and really doesn't add anything making the solution look more complex than the regex one. Still, +1 for avoiding regex to do simple things.
@BillK Thanks for the +1, but I wholeheartedly disagree. StringBuilder is specifically designed for repetitive string concatenation/manipulation, and is great for this purpose since we can predefine it's size (because we know what it's going to be just from s). There would likely be a distinct performance discrepancy if we used normal strings here (for larger inputs, of course).
I took apart the String impelmentation to analyze performance once. String already does something similar internally. The performance ganes are non-existant in many cases and in others are much less than you'd think. On top of that the point here is to come up with a clean, readable solution and performance doesn't fit into that anywhere (Even in production code, you should see and document a performance problem before you trade readability for performance).
@BillK String does something similar internally (actually not string per se, but the compiler) for one-time concatenations, not when the operation is being done within a loop. One primary difference is that string concatenations within loops create numerous new objects, whereas with StringBuilder all we need is a single instance. There's a lot of unnecessary copying that needs to be done when using plain strings. Beyond that, I personally don't think there is a readability issue with the code above.
You are absolutely right, however it seems that you don't understand how miniscule the gain is or how much it complicates the solution. I usually consider the constant use of StringBuilder a way to identify someone who doesn't understand the importants of readability and costs of premature optimization. However it's your answer and technically you totally know your stuff--I still think it's the best answer here :) Also either way is probably 100x faster than any regex-based solution.
2

What you ask is not possible; the way find works, each match can only match after the end of the previous match. However, you can write:

final Pattern p = Pattern.compile("[^/]/");
final String s = "/accounts/main/index/page.xhtml";
final Matcher m = p.matcher(s);
while (m.find()) {
    System.out.println(s.substring(0, m.end()));
}

Or, to get an array:

final Pattern p = Pattern.compile("[^/]/");
final String s = "/accounts/main/index/page.xhtml";
final Matcher m = p.matcher(s);
final List<String> resultList = new ArrayList<String>();
while (m.find()) {
    resultList.add(s.substring(0, m.end()));
}
final String[] resultArr = resultList.toArray(new String[resultList.size()]);

(Disclaimer: not tested.)

1 Comment

Well i though i could use groups or something of that kind. But since this does what i want, i am going to accept this as the correct answer.
1

Another way:

Pattern p = Pattern.compile("/[^/]+"); 
String s = "/accounts/main/index/page.xhtml";
String dir = "";
Matcher m = p.matcher(args[0]);
while(m.find()){
  dir += m.group();
  System.out.println(dir + "/");
}

Comments

0

It is actually possible to do it with regular expressions this will work for your example:

Pattern p = Pattern.compile("^(((/[^/]+/)[^/]+/)[^/]+/)");
String s = "/accounts/main/index/page.xhtml";
Matcher m = p.matcher(s);
while (m.find())
{
    System.out.println(m.group(1));
    System.out.println(m.group(2));
    System.out.println(m.group(3));
}

nevertheless, you can't have a regular expression that matches every single case. But as the structure of the regular expresion is well defined you can certainly build it on the fly depending on how deep is your directory structure, and then compile it each time.

2 Comments

Yes, when i know the level of my directory, this is ok. The purpose of my question is mostly because i do not know how deep the level of the directory can be.
@maress Oh, I see, you can't know how deep is your directory by counting the '/' character?
0

Regex is ok to split initially, but you have to add some code:

String parts = a.split("(?<!^)(?=/)");
for (int i = 0; i < parts.length - 2; i++)
    parts[i + 1] = parts[i] + parts[i + 1];

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.