Reputation: 227
I have a string as follows:
String sentence = "I have bananas\r" +
"He has apples\r" +
"I own 3 cars\n" +
"*!"
I'd like to reverse this string so as to have an output like this:
"*!" +
"\ncars 3 own I" +
"\rapples has He" +
"\rbananas have I"
Here is a program I wrote.
public static String reverseWords(String sentence) {
StringBuilder str = new StringBuilder();
String[] arr = sentence.split(" ");
for (int i = arr.length -1; i>=0; i--){
str.append(arr[i]).append(" ");
}
return str.toString();
}
But I don't get the output as expected. What is wrong?
Upvotes: 2
Views: 623
Reputation: 72344
Take a look at the output you're after carefully. You actually need two iteration steps here - you first need to iterate over all the lines backwards, then all the words in each line backwards. At present you're just splitting once by space (not by new line) and iterating over everything returned in that backwards, which won't do what you want!
Take a look at the example below - I've kept closely to your style and just added a second loop. It first iterates over new lines (either by \n or by \r, since split()
takes a regex), then by words in each of those lines.
Note however this comes with a caveat - it won't preserve the \r
and the \n
. For that you'd need to use lookahead / lookbehind in your split to preserve the delimiters (see here for an example.)
public static String reverseWords(String sentence) {
StringBuilder str = new StringBuilder();
String[] lines = sentence.split("[\n\r]");
for (int i = lines.length - 1; i >= 0; i--) {
String[] words = lines[i].split(" ");
for (int j = words.length - 1; j >= 0; j--) {
str.append(words[j]).append(" ");
}
str.append("\n");
}
return str.toString();
}
Upvotes: 1
Reputation: 4957
The problem is you are only splitting on spaces, but that is not the only type of whitespace in your sentence. You can use the pattern \s
to match all whitespace. However, then you don't know what to put back in that position after the split. So instead we will split on the zero-width position in front of or behind a whitespace character.
Change your split to this:
String[] arr = sentence.split("(?<=\\s)|(?=\\s)");
Also, now that you are preserving the whitespace characters, you no longer need to append them. So change your append to this:
str.append(arr[i]);
The final problem is that your output will be garbled due to the presence of \r
. So, if you want to see the result clearly, you should replace those characters. For example:
System.out.println(reverseWords(sentence).replaceAll("\\r","\\\\r").replaceAll("\\n","\\\\n"));
This modified code now give the desired output.
Output:
*!\ncars 3 own I\rapples has He\rbananas have I
Note:
Since you are freely mixing \r
and \n
, I did not add any code to treat \r\n
as a special case, which means that it will be reversed to become \n\r
. If that is a problem, then you will need to prevent or undo that reversal.
For example, this slightly more complex regex will prevent us from reversing any consecutive whitespace characters:
String[] arr = sentence.split("(?<=\\s)(?!\\s)|(?<!\\s)(?=\\s)");
The above regex will match the zero-width position where there is whitespace behind but not ahead OR where there is whitespace ahead but not behind. So it won't split in the middle of consecutive whitespaces, and the order of sequences such as \r\n
will be preserved.
Upvotes: 3
Reputation: 7838
The logic behind this question is simple, there are two steps to achieve the OP's target:
spaces
);Instead of using StringBuilder
, I'd prefer char[]
to finish this, which is easy to understand.
The local test code is:
public class WordReverse {
public static void main(String... args) {
String s = " We have bananas\r" +
"He has apples\r" +
"I own 3 cars\n" +
"*!";
System.out.println(reverseSentenceThenWord(s));
}
/**
* return itself if the @param s is null or empty;
* @param s
* @return the words (non-whitespace character compound) reversed string;
*/
private static String reverseSentenceThenWord(String s) {
if (s == null || s.length() == 0) return s;
char[] arr = s.toCharArray();
int len = arr.length;
reverse(arr, 0, len - 1);
boolean inWord = !isSpace(arr[0]); // used to track the start and end of a word;
int start = inWord ? 0 : -1; // is the start valid?
for (int i = 0; i < len; ++i) {
if (!isSpace(arr[i])) {
if (!inWord) {
inWord = true;
start = i; // just set the start index of the new word;
}
} else {
if (inWord) { // from word to space, we do the reverse for the traversed word;
reverse(arr, start, i - 1);
}
inWord = false;
}
}
if (inWord) reverse(arr, start, len - 1); // reverse the last word if it ends the sentence;
String ret = new String(arr);
ret = showWhiteSpaces(ret);
// uncomment the line above to present all whitespace escape characters;
return ret;
}
private static void reverse(char[] arr, int i, int j) {
while (i < j) {
char c = arr[i];
arr[i] = arr[j];
arr[j] = c;
i++;
j--;
}
}
private static boolean isSpace(char c) {
return String.valueOf(c).matches("\\s");
}
private static String showWhiteSpaces(String s) {
String[] hidden = {"\t", "\n", "\f", "\r"};
String[] show = {"\\\\t", "\\\\n", "\\\\f", "\\\\r"};
for (int i = hidden.length - 1; i >= 0; i--) {
s = s.replaceAll(hidden[i], show[i]);
}
return s;
}
}
The output is not in my PC as OP provided but as:
*!
bananas have I
However, if you set a breakpoint and debug it and check the returned string, it will be as:
which is the right answer.
Now, if you would like to show the escaped
whitespaces, you can just uncomment this line before returning the result:
// ret = showWhiteSpaces(ret);
And the final output will be exactly the same as expected in the OP's question:
*!\ncars 3 own I\rapples has He\rbananas have I
Upvotes: 1