Here is the question followed by my working program:
Given a string, return a version without the first 2 chars. Except keep the first char if it is 'a' and keep the second char if it is 'b'. The string may be any length. Harder than it looks.
deFront("Hello") → "llo" deFront("java") → "va" deFront("away") → "aay"
Can this code be improved?
public String deFront(String str) {
if (str.length() >= 2) {
char firstCharacter = str.charAt(0);
char secondCharacter = str.charAt(1);
if (firstCharacter == 'a' && secondCharacter == 'b') {
// donot skip
return str;
} else if (firstCharacter == 'a' && secondCharacter != 'b') {
// skip first character only
return str.substring(0, 1) + str.substring(2);
} else if (firstCharacter != 'a' && secondCharacter == 'b') {
// skip second character only
return str.substring(1);
} else {
return str.substring(2);
}
} else if (str.length() == 1 && str.charAt(0) == 'a') {
return str;
} else {
return str;
}
}
-
\$\begingroup\$ Yes. Fix the indentation so it's easier to read. \$\endgroup\$Joe F– Joe F2013年05月11日 14:57:48 +00:00Commented May 11, 2013 at 14:57
-
\$\begingroup\$ Also please fix the title and replace it with something meaningful. \$\endgroup\$Bobby– Bobby2013年05月11日 16:34:03 +00:00Commented May 11, 2013 at 16:34
-
1\$\begingroup\$ You should write some unit tests to check your function. \$\endgroup\$tb-– tb-2013年05月11日 17:03:40 +00:00Commented May 11, 2013 at 17:03
6 Answers 6
Just recoded it, doing simply what your explanation said
Except keep the first char if it is 'a' and keep the second char if it is 'b'.
Since a possible deletion of a character will affect indices, we check the second character before we check the first.
The resulting code is much simpler and readable simply because the extracted method deleteCharIfNotEqualTo() does away with some code duplication and at the same time heightens readability of the deFront() method, by explaining what is happening.
public String deFront(String str) {
StringBuilder builder = new StringBuilder(str);
deleteCharIfNotEqualTo(builder, 1, 'b');
deleteCharIfNotEqualTo(builder, 0, 'a');
return builder.toString();
}
private void deleteCharIfNotEqualTo(StringBuilder builder, int index, char character) {
if (builder.length() > index && character != builder.charAt(index)) {
builder.deleteCharAt(index);
}
}
How about this?
public static String deFront(String str)
{
String one = (str.length() > 0 && str.charAt(0) == 'a') ? "a" : "";
String two = (str.length() > 1 && str.charAt(1) == 'b') ? "b" : "";
return one+two+((str.length() > 2) ? str.substring(2) : "");
}
Note that only the parentheses around the entire ternary operation in the return are actually necessary, the others are for readability, as per @abuzittingillifirca's suggestion.
-
\$\begingroup\$ This throws a
StringIndexOutOfBoundsExceptionfor strings of length 0 and 1. \$\endgroup\$Joe F– Joe F2013年05月13日 16:54:20 +00:00Commented May 13, 2013 at 16:54 -
\$\begingroup\$ True. I only tested strings of length 2. Fixed my code above. Sorry for all the ternary crap, but I find a bunch of ifs would needlessly clutter this simple function. \$\endgroup\$BenderBoy– BenderBoy2013年05月14日 06:05:22 +00:00Commented May 14, 2013 at 6:05
-
\$\begingroup\$ Why create substrings for single character equality tests, rather than, e.g,
str.charAt(0)=='a'? \$\endgroup\$Cornelius Dol– Cornelius Dol2013年05月14日 06:40:26 +00:00Commented May 14, 2013 at 6:40 -
\$\begingroup\$ +1 for closely matching the description, conciseness and robustness (w.r.t. @bowmore's order dependent deletion). In addition to @SoftwareMonkey's
charAtcritique: Ternary operators are OK, But use parentheses and spaces around operators to improve readability of expressions. Also you should declare variables where they are assigned, this is not C or javascript. Use English names whenever international readers may possibly see the code (that is always on codereview). \$\endgroup\$abuzittin gillifirca– abuzittin gillifirca2013年05月14日 07:27:54 +00:00Commented May 14, 2013 at 7:27 -
\$\begingroup\$ Thanks! Edited. I had actually used chars and
charAtfirst, but since you can't have empty chars, I switched. You're right, chars do fine for the comparison. \$\endgroup\$BenderBoy– BenderBoy2013年05月14日 08:11:05 +00:00Commented May 14, 2013 at 8:11
First, is this part correct?
} else if (str.length() == 1 && str.charAt(0) == 'a') {
return str;
} else {
return str;
}
This will return a string of length 1, unchanged, regardless of the character. What should happen if you pass "z"? Since the first character is not 'a', should it be removed?
I suspect it was supposed to be something like this:
} else if (str.length() == 1 && str.charAt(0) != 'a') {
return "";
} else {
return str;
}
The rest of this answer assumes it should remove the character
You could use a StringBuilder and delete the characters as necessary:
public String deFront(String str) {
StringBuilder sb = new StringBuilder(str);
if (sb.length() > 1 && sb.charAt(1) != 'b') {
sb.deleteCharAt(1);
}
if (sb.length() > 0 && sb.charAt(0) != 'a') {
sb.deleteCharAt(0);
}
return sb.toString();
}
Or you could use a regular expression search & replace:
public String deFront(String str) {
str = str.replaceFirst("^(.)[^b]", "1ドル");
str = str.replaceFirst("^[^a]", "");
return str;
}
Note: The order of deletion is important in both of these solutions. The b is deleted first to prevent the deletion of a from shifting the b by one position.
You could do a number of interesting things with this problem. I chose to have a little fun with the fact that A and B are basically self-indicators of which position in the string they may stay in. It also allows you to expand the progressive character search throughout any length of string with just a one-line change. Forgive if my syntax isn't correct, my native language is C#.
public String deFront(String str) {
StringBuilder sb = new StringBuilder();
int a = (int)'a';
for(int i = 0; i < 2 && i < str.length()) {
char cur = str.charAt(i);
if (i < 2 && (int)cur - a != i) {
continue;
}
sb.append(cur);
}
return sb.toString();
}
And to apply this to the entire alphabet (repeating after the 26th character):
public String deFront(String str) {
StringBuilder sb = new StringBuilder();
int a = (int)'a';
for(int i = 0; i < 2 && i < str.length()) {
char cur = str.charAt(i);
if ((int)cur - a != i % 26) { // changed this line here
continue;
}
sb.append(cur);
}
return sb.toString();
}
Well, this algorithm can be at least improved by adding option to check for any list characters at position 0 and 1.
public class DeFront {
//if character is one of these, it will be removed
private static char[] firstLetterExcluded = "a".toCharArray();
private static char[] secondLetterExcluded = "bcdef".toCharArray();
public static String deFront(String str) {
boolean removeFirst = false, removeSecond = false;
for (char c : firstLetterExcluded) {
if (str.charAt(0) == c) {
removeFirst = true;
break;
}
}
for (char c : secondLetterExcluded) {
if (str.charAt(1) == c) {
removeSecond = true;
break;
}
}
//Before calling the stripping method, it's better to check if we need to
if (removeFirst || removeSecond) {
return stripString(str, removeFirst, removeSecond);
} else {
return str;
}
}
private static String stripString(String str, boolean removeFirst, boolean removeSecond) {
if (removeFirst && removeSecond) {
return str.substring(2);
} else if (removeFirst) {
return str.substring(1);
} else {
//Using Stringbuilder takes more time
return str.charAt(0) + str.substring(2);
}
}
}
Not necessarily efficient, but compact:
public static String deFront(String s)
{
return Pattern.compile("(?:(a)|[^a])(?:(?:(b)|[^b])(.*))?").matcher(s).replaceFirst("1ドル2ドル3ドル");
}
Note that there is a significant performance penalty with this solution, so don't use it if you call the function frequently. This is just to show an alternative using a regular expression.
-
\$\begingroup\$ why would you do this instead of what the original post does? please put that into your answer. thank you \$\endgroup\$Malachi– Malachi2015年06月17日 16:52:45 +00:00Commented Jun 17, 2015 at 16:52