2
\$\begingroup\$

The challenge: Given a String, for each digit in that original string, replace that digit with that many occurrences of the character following it. So a3tx2z yields attttxzzz.

My Solution:

import java.util.Scanner;

public class Standford1 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        System.out.print("Enter string to blowup: ");
        String userInput = input.nextLine();
        System.out.println(blowup(userInput)); 
    }

    public static String blowup(String str) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            if (!isNumeric(str.substring(i, i + 1))) {
                sb.append(str.charAt(i));
            }
            else {
                sb.append(lengthen(str.charAt(i + 1), 
                    Integer.parseInt(str.substring(i, i + 1))));
            }
        }
        return sb.toString();
    }

    public static boolean isNumeric(String str) {
        try {
            int num = Integer.parseInt(str);
        }
        catch(NumberFormatException nfe) {
            return false;
        }
        return true;
    }

    public static String lengthen(char c, int i){
        if (i == 0 || c == ' ') { return ""; }
        int count = 0;
        StringBuilder sb = new StringBuilder(c);
        while (count != i) {
            sb.append(c);
            count++;
        }
        return sb.toString();
    }
}

Could this be more readable? I alternate a lot between char and string (it seems optimal, am I wrong?), but should I just stick with one? Any and all feedback/suggested revisions/general optimizations welcomed with gratitude.

Edit: Stay within the specifications of the challenge. In the case of a number following a number this code is WAI i.e. the correct yield of blowup("a231") results in "a33111."

Edit 2: End digits just get ignored, since they have nothing to repeat, i.e. blowup("a2x3") would yield "axxx" - I realize I overlooked this case due to a question below, I have now amended this, but left the original question and code intact.

Edit 3: These should all be true:

/* Two plain cases */
System.out.println("Test 1: " + "xxaaaabb".equals(blowup("xx3abb")));
System.out.println("Test 2: " + "xxxZZZZ".equals(blowup("2x3Z")));
/* digit at the end */
System.out.println("Test 3: " + "axxx".equals(blowup("a2x3")));
/* Adjacent digits */
System.out.println("Test 4: " + "a33111".equals(blowup("a231")));
/* Digit include 0 */
System.out.println("Test 5: " + "aabb".equals(blowup("aa0bb")));
/* Unusual Characters */
System.out.println("Test 6: " + "AB&&,- ab".equals(blowup("AB&&,- ab")));
/* Empty String */
System.out.println("Test 7: " + "".equals(blowup("")));
/* Only digits */
System.out.println("Test 8: " + "".equals(blowup("2")));
System.out.println("Test 9: " + "33".equals(blowup("23")));

Note: So this isn't incorrectly perceived as a 'do this for me' question, as stated in edit 2, I've already accounted for all cases.

\$\endgroup\$
5
\$\begingroup\$

Your instincts are good here, in almost all respects. Your basic algorithm is good, and your suspicions that you are changing too often between String an char is also good.

Using the StringBuilder is the right thing to do, well done. Breaking the String in to chars is also good, but the way you do that is inconvenient.... In part that's because there is a convenient function in the standard Java Character class that you will find useful: Character.isDigit(char)

With that function, you can throw out your isNumber() function, and simplify the whole thing down to chars, strings, and ints.... something like:

public static String blowup(String str) {
    StringBuilder sb = new StringBuilder();
    int repeat = 0;
    for (char c : str.toCharArray()) {
        if (Character.isDigit(c)) {
            repeat = repeat * 10 + Character.getNumericValue(c);
        } else {
            while (repeat > 0) {
                sb.append(c);
                repeat--;
            }
            sb.append(c);
        }
    }
    return sb.toString();
}

(I put this in an Ideone here)

Note that it also keeps a counter outside the loop. This removes the need for the 'ugly' i + 1 offset on a repeating character count.

The code still assumes valid input (where the last character cannot be a digit, or it is lost). Additionally, I tweaked it to handle multiple digit input.... like 20z ... for fun.

Update: In my mind, I immediately 'threw out' your isNumeric() method, because it was not needed... but I have only now just read it.... Eeugh! Yuck!

You should never use exception handling (try/catch) to handle normal program flow. The try/parse/catch logic you have is very slow to run. Throwing an exception for every non-digit character will be a lot of overhead. Even though Java has improved in terms of performance, when processing exceptions, it is still much slower than the alternatives, like a regular expression, or some other mechanism...

Update 2: working with the original specs (thus ignoring the multi-digit repeat), the code would be (here in a different Ideone):

public static String blowup(String str) {
    StringBuilder sb = new StringBuilder();
    int repeat = -1;
    for (char c : str.toCharArray()) {
        if (repeat < 0 && Character.isDigit(c)) {
            repeat = Character.getNumericValue(c);
        } else {
            do {
                sb.append(c);
            } while (--repeat >= 0);
        }
    }
    return sb.toString();
}

Notes for this version:

  1. Use the 'repeat' as a flag set to negative for nothing. This allows for input like ab01 which will output ab1 (which would be impossible without the 0-repeat option).
  2. Note that the repeat is now also used as a flag for the isDigit check.
  3. a do-while loop instead of a while-do loop (as per Josay's comment).
\$\endgroup\$
  • \$\begingroup\$ Nice answer ! Two quick questions about the while-do loop : 1. Any reason for it not to be a for loop ? (as it does look like a for loop) 2. Any reason for it not to be a do-while ? (as it would remove the duplicated line of code) \$\endgroup\$ – Josay Dec 29 '14 at 0:44
  • \$\begingroup\$ @Josay - I considered the do-while loop, but found it woud be awkward to get the decrementing 'repeat' counter to work without it becoming negative, and impacting the 'sexy' double-digit handling. As for the 'for' loop, there is no need for an additional variable, so it seems gratuitous, but I would not be against it... \$\endgroup\$ – rolfl Dec 29 '14 at 0:47
  • 2
    \$\begingroup\$ Your 'fun' part is actually outside the specifications of the challenge. blowup("a231") should result in "a33111". \$\endgroup\$ – Legato Dec 29 '14 at 2:19
  • \$\begingroup\$ This made me realize this code doesn't accurately account for whether the last digit is a number though, thank you. \$\endgroup\$ – Legato Dec 29 '14 at 6:23
  • 1
    \$\begingroup\$ @Legato - added a second update to my answer. \$\endgroup\$ – rolfl Dec 29 '14 at 7:19
1
\$\begingroup\$

A few inputs:

  • Are you handling exceptional cases as in say,

    1. A string ends with a number.
    2. A string contains character other than {0-9,a-z,A-Z}
    3. Null or empty string.
  • Is there any upper limit to the value of number say, if the string is "a99999999". Since, your implementation expects one number after a character, is it a limit defined by you or is it pre-decided?

  • Be uniform with your approach, i.e Integer.parseInt has a try..catch in isNumeric and not in blowUp()
  • Also, charAt() at some places and substring at some?

I would go by the code of rolfl. It deftly handles all the behaviour.

\$\endgroup\$
  • \$\begingroup\$ I asked about that. I preferred charAt in all places available, for obvious reasons, but the parseInt method takes a str, I wasn't aware of the Character.isDigit method for the first use and otherwise my Lengthen method needed parseInt for its second parameter, but given the char method I looked it up and found: stackoverflow.com/questions/4968323/… \$\endgroup\$ – Legato Dec 29 '14 at 6:17

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.