6
\$\begingroup\$

I am looking for feedback on a solution to the following problem posed from a book that I'm working through (Java: How To Program 9th Edition):

Continuing the discussion in Exercise 16.20, we reiterate the importance of designing check-writing systems to prevent alteration of check amounts. One common security method requires that the amount be written in numbers and spelled out in words as well. Even if someone is able to alter the numerical amount of the check, it’s extremely difficult to change the amount in words. Write an application that inputs a numeric check amount that’s less than £1000 and writes the word equivalent of the amount. For example, the amount 112.43 should be written as

ONE hundred TWELVE and 43 pence

I'm interested in solutions which cover the areas I have studied so far, those being:

  1. Introduction to Computers and Java
  2. Introduction to Java Applications
  3. Introduction to Classes, Objects, Methods and Strings
  4. Control Statements: Part 1
  5. Control Statements: Part 2
  6. Methods: A Deeper Look
  7. Arrays and ArrayLists
  8. Classes and Objects: A Deeper Look
  9. Object-Oriented Programming: Inheritance
  10. Object-Oriented Programming: Polymorphism
  11. Exception Handling: A Deeper Look
  12. GUI Components: Part 1 (Swing)
  13. Strings, Characters and Regular Expressions

I have been a bit naughty by jumping ahead of the text to use a little recursion within the readMe() method. I figured from the description of recursion that the line of code in question would behave this way and I was right.

I can't help feeling that the way I have implemented the readMe() method is somewhat messy and I can see that it would not scale very well beyond £1000 or four digits. Is there a simpler way of doing this? All forms of feedback are welcome.

import java.util.Scanner;

public class WordCheckAmount {
public static void main(String[] args) {
    Scanner sc = new Scanner( System.in );
    String userInput;

    boolean correctInput = false;

    do
    {
        System.out.println( "Please enter an amount below 1000" );
        userInput = sc.nextLine();
        System.out.println();
        if( userInput.indexOf( '.' ) < 0 && validateAmount( userInput ) )   // no decimal point
        {
            correctInput = true;
            readMe( userInput );
        }
        if( userInput.indexOf( '.' ) > 0 && validatePointAmount( userInput ) )  // decimal point
        {
            correctInput = true;
            String[] tokens = userInput.split( "\\." );
            readMe( tokens[ 0 ] );
            System.out.printf( "and " );
            readMe( tokens[ 1 ] );
            System.out.println( "pence" );
        }
    }while( !correctInput );
}

public static boolean validatePointAmount( String amount )
{
    return amount.matches( "\\d{0,3}.?\\d{0,2}" );
}

public static boolean validateAmount( String amount )
{
    return amount.matches( "\\d{0,3}" );
}

public static void readMe( String amount )
{
    String[] oneThruNineteen = { "NULL", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX",
                            "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN", "TWELVE",
                            "THIRTEEN", "FOURTEEN", "FIFTEEN", "SIXTEEN", "SEVENTEEN",
                            "EIGHTEEN", "NINETEEN" };

    String[] theDecades = { "NULL", "NULL", "TWENTY", "THIRTY", "FOURTY", "FIFTY",
                            "SIXTY", "SEVENTY", "EIGHTY", "NINETY" };

    int theLength = amount.length();

    switch( theLength )
    {
        case 3:     // 3 digits
            System.out.printf( "%s ", oneThruNineteen[ Character.getNumericValue( amount.charAt( 0 ) ) ] );
            System.out.printf( "hundred " );
            int testMe = Character.getNumericValue( amount.charAt( 1 ) );
            if( testMe < 2 )            // if tens unit in the teens
                System.out.printf( "%s ", oneThruNineteen[ Integer.parseInt( amount.substring( 1, 3 ) ) ] );
            else                        // if tens unit twenty or higher
            {
                String feedMe = amount.substring( 1 , 3 );
                readMe( feedMe );       // recursion I presume? feedback for 2 digits
            }
            break;
        case 2:     // 2 digits
            int testMe2 = Character.getNumericValue( amount.charAt( 0 ) );
            if( testMe2 < 2 )           // if tens unit in the teens
                System.out.printf( "%s ", oneThruNineteen[ Integer.parseInt( amount ) ] );
            else                        // if tens unit twenty or higher
            {
                System.out.printf( "%s ", theDecades[ Character.getNumericValue( amount.charAt( 0 ) ) ] );
                int testMe3 = Character.getNumericValue( amount.charAt( 1 ) );
                if( testMe3 > 0 )       // if next digit is higher than 0, otherwise you get TWENTY NULL for 20 etc
                    System.out.printf( "%s ", oneThruNineteen[ Character.getNumericValue( amount.charAt( 1 ) ) ] );
            }
            break;
        case 1:     // just the 1 digit
            System.out.printf( "%s ", oneThruNineteen[ Integer.parseInt( amount ) ] );
            break;
    }
}
}
\$\endgroup\$
7
\$\begingroup\$

Yes! Your code is more complicated than it needs to be.

  • You treat the String as a String instead of first parsing it as a number and treating it as a number. It is more complicated to treat chars than numbers when you want to use them as numbers.
  • You have some code duplication near the logic of if (testMe < 2) ...
  • Instead of printing directly, it is better to return a String and let the caller print

Here is a method I came up with, that returns a string:

static String[] oneThruNineteen = { "NULL", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX",
        "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN", "TWELVE",
        "THIRTEEN", "FOURTEEN", "FIFTEEN", "SIXTEEN", "SEVENTEEN",
        "EIGHTEEN", "NINETEEN" };

static String[] theDecades = { "NULL", "NULL", "TWENTY", "THIRTY", "FOURTY", "FIFTY",
        "SIXTY", "SEVENTY", "EIGHTY", "NINETY" };
public static String numToStr(int amount) {
    String str = "";
    if (amount >= 100) {
        str += oneThruNineteen[ amount / 100] + " hundred ";
        str += numToStr(amount % 100);
    }
    else if (amount >= 20) {
        str += theDecades[  amount / 10 ];
        str += numToStr(amount % 10);
    }
    else {
        str += oneThruNineteen[ amount ];
    }
    return str;
}

It is possible to improve even further to add thousands, millions, etc.

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

The main body of could is really easy to be written as a recursive program:

final private  static String[] units = {"Zero","One","Two","Three","Four",
    "Five","Six","Seven","Eight","Nine","Ten",
    "Eleven","Twelve","Thirteen","Fourteen","Fifteen",
    "Sixteen","Seventeen","Eighteen","Nineteen"};
final private static String[] tens = {"","","Twenty","Thirty","Forty","Fifty",
    "Sixty","Seventy","Eighty","Ninety"};

public static String convert(Integer i) {
    if( i < 20)  return units[i];
    if( i < 100) return tens[i/10] + ((i % 10 > 0)? " " + convert(i % 10):"");
    if( i < 1000) return units[i/100] + " Hundred" + ((i % 100 > 0)?" and " + convert(i % 100):"");
    if( i < 1000000) return convert(i / 1000) + " Thousand " + ((i % 1000 > 0)? " " + convert(i % 1000):"") ;
    return convert(i / 1000000) + " Million " + ((i % 1000000 > 0)? " " + convert(i % 1000000):"") ;
}

Now just cover pence aswell:

public static String convert(Double i) {
    double pence = (int)((i-((int)i))*100);
    string result = convert((int)i);
    return pence.Equals(0d) ? result : result + " and " + pence " pence";
}
\$\endgroup\$

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.