4
\$\begingroup\$

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;
    }
}
\$\endgroup\$
  • \$\begingroup\$ Yes. Fix the indentation so it's easier to read. \$\endgroup\$ – Joe F May 11 '13 at 14:57
  • \$\begingroup\$ Also please fix the title and replace it with something meaningful. \$\endgroup\$ – Bobby May 11 '13 at 16:34
  • 1
    \$\begingroup\$ You should write some unit tests to check your function. \$\endgroup\$ – tb- May 11 '13 at 17:03
10
\$\begingroup\$

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);
    }
}
\$\endgroup\$
5
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ This throws a StringIndexOutOfBoundsException for strings of length 0 and 1. \$\endgroup\$ – Joe F May 13 '13 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 May 14 '13 at 6:05
  • \$\begingroup\$ Why create substrings for single character equality tests, rather than, e.g, str.charAt(0)=='a'? \$\endgroup\$ – Lawrence Dol May 14 '13 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 charAt critique: 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 May 14 '13 at 7:27
  • \$\begingroup\$ Thanks! Edited. I had actually used chars and charAt first, but since you can't have empty chars, I switched. You're right, chars do fine for the comparison. \$\endgroup\$ – BenderBoy May 14 '13 at 8:11
4
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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();
}
\$\endgroup\$
1
\$\begingroup\$

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);
        }


    }

}
\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ why would you do this instead of what the original post does? please put that into your answer. thank you \$\endgroup\$ – Malachi Jun 17 '15 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.