Solution critique: reversing words in a sentence - java

I had a technical interview and was given the following question:
Write a function that takes a sentence and returns the sentence with the words in reverse order (i.e. "hello world" becomes "world hello").
Here is the solution that I gave in Java:
/** Takes a sentence as an input and returns the sentence with the words in
* reversed order. */
private static String reverseSentence(String sentence) {
String[] words = sentence.split("\\s+");
String reversedString = "";
for (int k = words.length - 1; k >= 0; k -= 1) {
reversedString += " " + words[k];
}
return reversedString.substring(1);
}
I got to thinking that there has to be a more efficient way to solve this problem than this. I don't see this being asked in a technical interview for a top company if this solution turns out to be the best one and there isn't one more efficient/elegant.
Can anyone think of a better way to do this?

Here are better ways to concatenate the elements of the sentence: What's the most elegant way to concatenate a list of values with delimiter in Java?
Also, sentence.split("\\s+") only works when the input is clean (people do make typos). There is also the questions what should happen to punctuation. World! Hello, does look very odd.

Except the comment, to use a more efficient data structure (StringBuilder) to create the string, I don't think there is much you can do. The time complexity to reverse an array is n/2 (How do I reverse an int array in Java?) but afterwards you would still need to concatenate all of the items, which should be n - since you're concatenating them right away you get down from 3n/2 to only n.

Once you have reversed the array by swapping references (0 with length-1, etc) you can
String reversedSentence = String.join( " ", words );
But I think it is better to avoid a String array.
String s = "the quick brown fox";
StringBuilder sb = new StringBuilder();
int p = s.length();
int np = -1;
while( (np = s.lastIndexOf( " ", p-1 )) != -1 ){
sb.append( s.substring( np + 1, p ) ).append( " " );
p = np;
}
sb.append( s.substring( 0, p ) );
System.out.println( sb.toString() );
This assumes that the input string is properly "normalized".

Sometimes this question is asked with a constraint asking for it to be done without using any extra memory.
One answer to this modified question is:
First reverse each word in the sentence ("Hello World" -> "olleH dlroW")
Then reverse the entire sentence ("olleH dlroW" -> "World Hello")

Related

How to remove a trailing comma from a string (Java)

I have an array, which I use a for each loop to iterate through. I then store it in a string and then try to add a comma to it, but am struggling look for a "logic" based solution.
I am almost close to about 1 year's worth of Java under my belt, so most of the solutions I am trying to find to implement are mostly logic based (for now), since this is apart of Java MOOC's course. What are some options I can look at? What am I missing?
for(int number: array){
String thread = "";
thread += number + ", ";
System.out.print(thread);
}
You can use a Stream to achieve this result.
System.out.println(Arrays.stream(array).collect(Collectors.joining(",")));
I'm not sure the constraints of this project for your course, but if you're able, try a StringJoiner! It will add a delimiter automatically to separate items that are added to it. Another note, I think you're going to want to declare your String outside of your for loop. otherwise it resets every iteration.
StringJoiner joiner = new StringJoiner(",");
for(int number : array){
joiner.add(String.valueOf(number));
}
System.out.print(joiner.toString());
What I like to do when I'm just doing something simple and quick is this:
String thread = "";
for (int i = 0; i < array.length; i++) {
int number = array[i];
thread += number;
if (i < array.length - 1) {
thread += ", ";
}
}
Basically all it does is check that we aren't on the last index and append the comma only if it isn't the last index. It's quick, simple, and doesn't require any other classes.
Pressuming you had a string ending with a comma, followed by zero or more white spaces you could do the following. String.replaceAll() uses a regular expression to detect the replacement part.
\\s* means 0 or more white spaces
$ means at the end of the line
String str = "a, a, b,c, ";
str = str.replaceAll(",\\s*$","");
Prints
a, a, b,c

if statement unintentionally prematurely terminated a for loop(Regex)

I'm trying to make sure that the first letters of the forename and surname strings are capital. I have some java code as follows and for the life of me I do not know why it only works on the first character in the stringbuffer and wont carry out the rest of the loop. I believe this is an error in my regex which i'm not quite clear on.
I'm 90% sure it's because of the space & colon presents in the original string.
the original string reads as
StringBuffer output = new StringBuffer(forename + ", " + surname);
Java
int length_of_names = Director.getSurname().length() + Director.getForename().length() + 2;
Pattern pattern = Pattern.compile("\\b([A-Z][a-z]*)\\b");
Matcher matcher = pattern.matcher(output.append(Director));
for(int i = 0; i < length_of_names; i++)
{
if (matcher.find() == true)
{
output.setCharAt(i, Character.toUpperCase(output.charAt(i)) );
continue;
}
}
A nice, quick 101 on regex statements and how to compose them would also be well appreciated
Disclaimer: This answer makes lots of assumptions. Purpose of answer is to show problems with code in question, which is relevant even if assumptions are wrong.
Assumptions:
Value of forename is same as returned by Director.getForename().
Value of surname is same as returned by Director.getSurname().
Value of output at time of matcher(...) call is as shown earlier.
Director.toString() is implemented as return surname + ", " + forename;. Exact implementation doesn't matter, but rest of answer assumes this implementation.
For purpose of illustration, forename = "John" and surname = "Doe".
Now, lets go thru the code and see what's going on:
StringBuffer output = new StringBuffer(forename + ", " + surname);
Value of output is now "John, Doe" (9 characters).
int length_of_names = Director.getSurname().length() + Director.getForename().length() + 2;
Value of length_of_names calculates to 9.
This could be done better using int length_of_names = output.length().
Pattern pattern = Pattern.compile("\\b([A-Z][a-z]*)\\b");
Matcher matcher = pattern.matcher(output.append(Director));
The string returned by Director.toString() ("Doe, John") is appended to output, resulting in value being "John, DoeDoe, John". That value is given to the matcher.
With that regex pattern, the matcher will find "John", and "John". It will not find "DoeDoe", since that has an uppercase letter in the middle.
Result is that find() returns true twice, and all subsequent calls will return false.
for(int i = 0; i < length_of_names; i++)
{
if (matcher.find() == true)
{
output.setCharAt(i, Character.toUpperCase(output.charAt(i)) );
continue;
}
}
Loop iterate 9 times, with values of i from 0 to 8 (inclusive).
First two iterations enters the if statement, so code will uppercase first two characters in output, resulting in value "JOhn, DoeDoe, John".
The continue statement has no effect, since loop continues anyway.
OOPS!!
That is not what code should do. So, to fix it:
Don't append Director to output.
Don't iterate 9 times. Instead, iterate until find() returns false.
Use position of the found text to locate the character to uppercase.
That makes code look like this:
StringBuffer output = new StringBuffer(forename + ", " + surname);
Pattern pattern = Pattern.compile("\\b([A-Z][a-z]*)\\b");
Matcher matcher = pattern.matcher(output);
while (matcher.find()) {
int i = matcher.start();
output.setCharAt(i, Character.toUpperCase(output.charAt(i)));
}
Of course, the code is still totally meaningless, since you matched words starting with uppercase letter, so changing the first letter to uppercase will do nothing at all.

Best way to search for a special string in a text

If I have a piece of text around 3000 characters. I want search for strings with certain characteristics for example strings like [*].
That is, I want to get [a] and [bc] from
sjfhshdkfjhskdhfksdf[a]sfdsgfsdf[bc]
I know there is an algorithm called KMP that guarantee a linear time searching operation through a text, but here I don't have a fixed string to be found, maybe I have to use some regular expression at some place.
How can I do this better than O(n^2)? Is there any light libraries for this if I'm using java?
No libraries needed, you've effectively described a use case for regex! They are highly optimized for searching, and in this case will be O(n).
String str = "sjfhshdkfjhskdhfksdf[a]sfdsgfsdf[bc]";
List<String> allMatches = new ArrayList<>();
Matcher m = Pattern.compile("\\[[^\\]]*]").matcher(str);
while (m.find()) {
allMatches.add(m.group());
}
Regex Demo
If you have any doubts though and really want some O(n) that you can see, here's an algorithm:
String str = "sjfhshdkfjhskdhfksdf[a]sfdsgfsdf[bc]";
List<String> allMatches = new ArrayList<>();
for (int i = str.indexOf('['), j; i != -1; i = str.indexOf('[', j + 1)) {
j = str.indexOf(']', i + 1);
// if `j` is -1, the brackets are unbalanced. Perhaps throw an Exception?
allMatches.add(str.substring(i, j + 1));
}
Here's how to do it in one line:
String[] hits = str.replaceAll("^.*?\\[|][^\\]]*$", "").split("].*?\\[");
This works by stripping off leading and trailing chars up to and including the first/last opening/closing square bracket, then splits on a close bracket to the next opening bracket (inclusive).

Splitting string in between two characters in Java

I am currently attempting to interpret some code I wrote for something. The information I would like to split looks something like this:
{hey=yes}TEST
What I am trying to accomplish, is splitting above string in between '}' and 'T' (T, which could be any letter). The result I am after is (in pseudocode):
["{hey=yes}", "TEST"]
How would one go about doing so? I know basic regex, but have never gotten into using it to split strings in between letters before.
Update:
In order to split the string I am using the String.split method. Do tell if there is a better way to go about doing this.
You can use String's split method, as follow:
String str = "{hey=foo}TEST";
String[] split = str.split("(?<=})");
System.out.println(split[0] + ", " + split[1]);
It splits the string and prints this:
{hey=foo}, TEST
?<=}, is to split after the character } and keep the character while doing it. By default, if you just split on a character, it will be removed by the split.
This other answer provides a complete explanation of all options when using the split method:
how-to-split-string-with-some-separator-but-without-removing-that-separator-in-j
Usage of regexp for such a small piece of code can be really slow, if it is repeated thousands of times (e.g. like analysing Alfresco metadata for lot of documents).
Look at this snippet:
String s = "{key=value}SOMETEXT";
String[] e = null;
long now = 0L;
now = new Date().getTime();
for (int i = 0; i < 3000000; i++) {
e = s.split("(?<=})");
}
System.out.println("Regexp: " + (new Date().getTime() - now));
now = new Date().getTime();
for (int i = 0; i < 3000000; i++) {
int idx = s.indexOf('}') + 1;
e = new String[] { s.substring(0, idx), s.substring(idx) };
}
System.out.println("IndexOf:" + (new Date().getTime() - now));
result is
Regexp: 2544
IndexOf:113
This means that regexp is 25 times slower than a (easier) substring. Keep it in mind: it can make the difference between a efficient code and a elegant (!) one.
If you're looking for a regex approach and also want some validation that input follows the expected syntax you probably want something like this:
public List<String> splitWithRegexp(String string)
{
Matcher matcher = Pattern.compile("(\\{.*\\})(.*)").matcher(string);
if (matcher.find())
return Arrays.asList(matcher.group(1), matcher.group(2));
else
throw new IllegalArgumentException("Input didn't match!");
}
The parenthesis in the regexp captures groups, which you can access with matcher.group(n) calls. Group 0 matches the whole pattern.

Can we split a Java String Backwards?

I have a string
Product Description ID1 ID2 QTY Rate1 Rate2
I want do break it as
Product Description|ID1|ID2|QTY|Rate1|Rate2
The only common delimiter I could see is space. But product description can be any number of words. So I don't know how many spaces it will have. But the spaces in rest of the string is fixed.
Currently I split it into array, get its length and concat appropriately. Is there a more direct way?
It is not entirely clear what you mean by "more directly", but I'm assuming that you want to avoid the concatenation step.
One way would be as follows:
make an initial pass using Matcher.find(int) to find the position of the "N - 5th" separator. (This requires an int[] with 5 elements that you use as a sort of queue for the offsets determined using find.)
use String.substring to extract the strings before and after that separator
use String.split to split the 2nd substring to separate the last 5 fields.
If you kept the offsets and match lengths (from step #1) you could replace the split call in step #3 with a loop and substring calls.
(The reason you can't in general split "backwards" is that regexes cannot be matched backwards. Or more precisely, if you did that you might end up splitting differently to when you did it "forwards". This makes it non-trivial to avoid doing 2 passes. Obviously in your specific case, the separator regex would be "one-or-more-whitespace" which would work in both directions ... if there was a capability to do that in the Matcher API.).
Split it into an array as normal
You will end up with an array with n + 5 components where n is the number of words in the product description.
Then you know:
Rate2 = arr[len-1]
Rate1 = arr[len-2]
QTY = arr[len-3]
ID2 = arr[len-4]
ID3 = arr[len-5]
Then combine elements arr[0] to arr[len-6] adding a space in between them. This gives you your product description.
Alternatively you could use Collections.reverse() on the output array but both methods get the same result
Using a StringBuilder, this is probably as compact as you can get. The good thing is you don't have to juggle a lot of indices around. In fact, you don't have to juggle any.
String foo = "Product Description Test Potato ID1 ID2 QTY Rate1 Rate2 ";
foo = foo.trim(); // get rid of any extra spaces, in case we have any, as a safety check
//the code that does your work
StringBuilder bar = new StringBuilder(foo);
// we have to add the pipe 5 times
for (int i = 5; i > 0; i--) {
bar.setCharAt(bar.lastIndexOf(" "), '|');
}
foo = bar.toString();
// Product Description ID1 ID2 QTY Rate1 Rate2
// Product Description|ID1|ID2|QTY|Rate1|Rate2
String actWord = "Product it a middle data Description ID1 ID2 QTY Rate1 Rate2";
String outputWord = null;
String[] arr = actWord.split(" ");
StringBuffer buf = new StringBuffer();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + "=" + (i < arr.length - 5));
if (i < arr.length - 5) {
buf.append(arr[i] + " ");
} else
buf.append("|" + arr[i]);
}
System.out.println("output - " + buf.toString().trim());
I hope this solves your req.
Reverse, the string.
Parse/Split based on space.
Re-reverse all the words as you pull them out of the collection of parsed/split words.
Not super elegant, but it should work.

Categories

Resources