I'm presently trying to understand a particular algorithm at the CodingBat platform.
Here's the problem presented by CodingBat:
*Suppose the string "yak" is unlucky. Given a string, return a version where all the "yak" are removed, but the "a" can be any char. The "yak" strings will not overlap.
Example outputs:
stringYak("yakpak") → "pak"
stringYak("pakyak") → "pak"
stringYak("yak123ya") → "123ya"*
Here's the official code solution:
public String stringYak(String str) {
String result = "";
for (int i=0; i<str.length(); i++) {
// Look for i starting a "yak" -- advance i in that case
if (i+2<str.length() && str.charAt(i)=='y' && str.charAt(i+2)=='k') {
i = i + 2;
} else { // Otherwise do the normal append
result = result + str.charAt(i);
}
}
return result;
}
I can't make sense of this line of code below. Following the logic, result would only return the character at the index, not the remaining string.
result = result + str.charAt(i);
To me it would make better sense if the code was presented like this below, where the substring function would return the letter of the index and the remaining string afterwards:
result = result + str.substring(i);
What am I missing? Any feedback from anyone would be greatly helpful and thank you for your valuable time.
String concatenation
In order to be on the same page, let's recap how string concatenation works.
When at least one of the operands in the expression with plus sign + is an instance of String, plus sign will be interpreted a string concatenation operator. And the result of the execution of the expression will be a new string created by appending the right operand (or its string representation) to the left operand (or its string representation).
String str = "allow";
char ch = 'h';
Object obj = new Object();
System.out.println(ch + str); // prints "hallow"
System.out.println("test " + obj); // prints "test java.lang.Object#16b98e56"
Explanation of the code-logic
That said, I guess you will agree that this statement concatenates a character at position i in the str to the resulting string and assigns the result of concatenation to the same variable result:
result = result + str.charAt(i);
The condition in the code provided by coding bat ensures whether the index i+2 is valid and then checks characters at indices i and i+2. If they are equal to y and k respectively. If that is not the case, the character will be appended to the resulting string. Athowise it will be discarded and the indexed gets incremented by 2 in order to skip the whole group of characters that constitute "yak" (with a which can be an arbitrary symbol).
So the resulting string is being constructed in the loop character by characters.
Flavors of substring()
Method substring() is overload, there are two flavors of it.
A version that expects two argument: the starting index inclusive, the ending index, exclusivesubstring(int, int).
And you can use it to achieve the same result:
// an equivalent of result = result + str.charAt(i);
result = result + str.substring(i, i + 1);
Another version of this method, that expects one argument will not be useful here. Because the result returned by str.substring(i) will be not a string containing a single character, but a substring staring from the given index, i.e. encompassing all the characters until the end of the string as documentation of substring(int) states:
public String substring(int beginIndex)
Returns a string that is a substring of this string. The substring
begins with the character at the specified index and extends to the
end of this string.
Examples:
"unhappy".substring(2) returns "happy"
"Harbison".substring(3) returns "bison"
"emptiness".substring(9) returns "" (an empty string)
Side note:
This coding-problem was introduced in order to master the basic knowledge of loops and string-operations. But actually the simplest to solve this problem is by using method replaceAll() that expects a regular expression and a replacement-string:
return str.repalaceAll("y.k", "");
Why does this code work?
public static String reverse(String a) {
if(a.length() == 0) {
return a;
} else {
return reverse(a.substring(1)) + a.charAt(0);
}
}
And this doesn't?:
public static String reverse(String a) {
if(a.length() == 0) {
return a;
} else {
return reverse(a.substring(1)) + a.substring(0);
}
}
Also, how does the recursion work in case 1, what does adding a.charAt(0) do? And how does this method ever reach the base case?
Because a.charAt(0) returns the first character, while a.substring(0) returns the entire String (from index 0). Change
return reverse(a.substring(1)) + a.substring(0);
to something like
return reverse(a.substring(1)) + a.substring(0, 1);
And it will work as expected.
To get better understanding about recursive code, you can try to print the state for each method calls, e.g.
public static String reverse(String a) {
System.out.println("Calling reverse(\"" + a + "\")");
if(a.length() == 0) {
System.out.println("Base case encountered for string : \"" + a + "\"");
return a;
} else {
String b = reverse(a.substring(1));
String c = a.charAt(0);
System.out.println(reverse(\"" + a + "\") returning \"" + b + "\" + \"" + c + "\"");
return b + c;
}
}
When you try to call reverse("xyz"), then you can see the following text printed within standard output:
Calling reverse("xyz")
Calling reverse("yz")
Calling reverse("z")
Calling reverse("")
Base case encountered for string : ""
reverse("z") returning "" + "z" = "z"
reverse("yz") returning "z" + "y" = "zy"
reverse("xyz") returning "zy" + "x" = "zyx"
We can see several things:
You reduce the string recursively until reaching the base case where the string is empty (has zero length).
For each non base case, you split the string into two segment, namely b and c. Then you return reverse(b) + c.
Firstly, 'a.substring()' returns the substring starting from the index given as parameter, so while using 'a.substring(1)' as the parameter of the recursive method the first character always gets skipped and the length of the string given as parameter decreases gradually. Once no character remains it reaches the base case.
Secondly, 'a.charAt()' returns the character exists at the index of the string given as parameter. So 'a.charAt(0)' returns the first index of the string 'a' which is given as the parameter of the recursive method.
Finally, the first code works because each time it sends the entire string except the first character and it includes that first character at the end of the string that is returned reversed. So at the end, the entire string gets reversed.
On the other hand, the second code includes the entire substring starting from the first index of the string given as its parameter instead of the first character.
To make the code work you can either use 'charAt(0)' like the first code -
return reverse(a.substring(1)) + a.charAt(0);
or you can use 'a.substring(0, 1)' which considers only the first character as the substring and returns it -
return reverse(a.substring(1)) + a.substring(0, 1);
byte[] a has value of {119}, which is the ascii equivalent of "w", but when I use .toString() to convert it to string, it gives me a weird string. any idea what I did wrong?
byte[] a = characteristicRX.getValue();
String rscvString = a.toString();
Log.d("byteToHex", "rscvString = " + rscvString);
while ( rscvString != "w" ){
String object takes a parameter of byte[] as an overloaded constructor. Use String rscvString = new String(a); and you should be sorted
You can't use boolean operators to test against strings ie. != or ==.
use while ( !(rscvString.equalsIgnoreCase("w") ) the equalsIgnoreCase() method will return a boolean and the ! will force the test against the false.
Try one of the lines below to cast a byte to a character and transform it to String
String rscvString = String.valueOf((char) a);
String rscvString = String.valueOf((char) (a & 0xFF));
You can pass a byte array in the String constructor to get a String object of your array.
String s="";
while ((strLine = br.readLine()) != null) {
s=s.concat(strLine);
When I used this code I get the string I expect from the file ..
But if I used
String s = null;
What I get is null as the result of the s string. Can anyone explain me the reason for this?
Firstly, I suspect that's not your code - or it would throw a NullPointerException. I suspect you've actually got:
s = s + strLine;
After that, it's very simple - concatenating any string with a null String reference will give you null:
String x = null;
String y = x + "a";
System.out.println(y); // nulla
From section 15.18.1 of the JLS (string concatenation):
If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other operand to produce a string at run time.
Then from section 5.1.11:
If the reference is null, it is converted to the string "null" (four ASCII characters n, u, l, l).
Note that your code is extremely inefficient at the moment - you should be using a StringBuilder.
I'm guessing I'm getting this error because the string is trying to substring a null value. But wouldn't the ".length() > 0" part eliminate that issue?
Here is the Java snippet:
if (itemdescription.length() > 0) {
pstmt2.setString(3, itemdescription.substring(0,38));
}
else {
pstmt2.setString(3, "_");
}
I got this error:
java.lang.StringIndexOutOfBoundsException: String index out of range: 38
at java.lang.String.substring(Unknown Source)
at MASInsert2.itemimport(MASInsert2.java:192)
at MASInsert2.processRequest(MASInsert2.java:125)
at MASInsert2.doGet(MASInsert2.java:219)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:627)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:269)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:172)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:174)
at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:835)
at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:640)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1286)
at java.lang.Thread.run(Unknown Source)
It is a pity that substring is not implemented in a way that handles short strings – like in other languages e.g. Python.
Ok, we cannot change that and have to consider this edge case every time we use substr, instead of if-else clauses I would go for this shorter variant:
myText.substring(0, Math.min(6, myText.length()))
I"m guessing i'm getting this error
because the string is trying to
substring a Null value. But wouldn't
the ".length() > 0" part eliminate
that issue?
No, calling itemdescription.length() when itemdescription is null would not generate a StringIndexOutOfBoundsException, but rather a NullPointerException since you would essentially be trying to call a method on null.
As others have indicated, StringIndexOutOfBoundsException indicates that itemdescription is not at least 38 characters long. You probably want to handle both conditions (I assuming you want to truncate):
final String value;
if (itemdescription == null || itemdescription.length() <= 0) {
value = "_";
} else if (itemdescription.length() <= 38) {
value = itemdescription;
} else {
value = itemdescription.substring(0, 38);
}
pstmt2.setString(3, value);
Might be a good place for a utility function if you do that a lot...
I would recommend apache commons lang. A one-liner takes care of the problem.
pstmt2.setString(3, StringUtils.defaultIfEmpty(
StringUtils.subString(itemdescription,0, 38), "_"));
You really need to check if the string's length is greater to or equal to 38.
Java's substring method fails when you try and get a substring starting at an index which is longer than the string.
An easy alternative is to use Apache Commons StringUtils.substring:
public static String substring(String str, int start)
Gets a substring from the specified String avoiding exceptions.
A negative start position can be used to start n characters from the end of the String.
A null String will return null. An empty ("") String will return "".
StringUtils.substring(null, *) = null
StringUtils.substring("", *) = ""
StringUtils.substring("abc", 0) = "abc"
StringUtils.substring("abc", 2) = "c"
StringUtils.substring("abc", 4) = ""
StringUtils.substring("abc", -2) = "bc"
StringUtils.substring("abc", -4) = "abc"
Parameters:
str - the String to get the substring from, may be null
start - the position to start from, negative means count back from the end of the String by this many characters
Returns:
substring from start position, null if null String input
Note, if you can't use Apache Commons lib for some reason, you could just grab the parts you need from the source
// Substring
//-----------------------------------------------------------------------
/**
* <p>Gets a substring from the specified String avoiding exceptions.</p>
*
* <p>A negative start position can be used to start {#code n}
* characters from the end of the String.</p>
*
* <p>A {#code null} String will return {#code null}.
* An empty ("") String will return "".</p>
*
* <pre>
* StringUtils.substring(null, *) = null
* StringUtils.substring("", *) = ""
* StringUtils.substring("abc", 0) = "abc"
* StringUtils.substring("abc", 2) = "c"
* StringUtils.substring("abc", 4) = ""
* StringUtils.substring("abc", -2) = "bc"
* StringUtils.substring("abc", -4) = "abc"
* </pre>
*
* #param str the String to get the substring from, may be null
* #param start the position to start from, negative means
* count back from the end of the String by this many characters
* #return substring from start position, {#code null} if null String input
*/
public static String substring(final String str, int start) {
if (str == null) {
return null;
}
// handle negatives, which means last n characters
if (start < 0) {
start = str.length() + start; // remember start is negative
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return EMPTY;
}
return str.substring(start);
}
substring(0,38) means the String has to be 38 characters or longer. If not, the "String index is out of range".
if (itemdescription != null && itemdescription.length() > 0) {
pstmt2.setString(3, itemdescription.substring(0, Math.min(itemdescription.length(), 38)));
} else {
pstmt2.setString(3, "_");
}
I'm assuming your column is 38 characters in length, so you want to truncate itemdescription to fit within the database. A utility function like the following should do what you want:
/**
* Truncates s to fit within len. If s is null, null is returned.
**/
public String truncate(String s, int len) {
if (s == null) return null;
return s.substring(0, Math.min(len, s.length()));
}
then you just call it like so:
String value = "_";
if (itemdescription != null && itemdescription.length() > 0) {
value = truncate(itemdescription, 38);
}
pstmt2.setString(3, value);
itemdescription is shorter than 38 chars. Which is why the StringOutOfBoundsException is being thrown.
Checking .length() > 0 simply makes sure the String has some not-null value, what you need to do is check that the length is long enough. You could try:
if(itemdescription.length() > 38)
...
You must check the String length. You assume that you can do substring(0,38) as long as String is not null, but you actually need the String to be of at least 38 characters length.
When this is appropriate, I use matches instead of substring.
With substring:
if( myString.substring(1,17).equals("Someting I expect") ) {
// Do stuff
}
// Does NOT work if myString is too short
With matches (must use Regex notation):
if( myString.matches("Someting I expect.*") ) {
// Do stuff
}
// This works with all strings
Should anyone face the same problem.
Do this:
str.substring (...(trim()) ;
Hope it helps somebodies 😎
You get this if itemdescription is shorter than 38 characters
You can look which exceptions are thrown and when in the JAVA API
in you case for String#substring(int,int): https://docs.oracle.com/javase/9/docs/api/java/lang/String.html#substring-int-int-
substring
public String substring(int beginIndex, int endIndex)
. . .
Throws:
IndexOutOfBoundsException
if the beginIndex is negative,
or endIndex is larger than the length of this String object,
or beginIndex is larger than endIndex.
(same applies to previous java versions as well)