8
\$\begingroup\$

Example:

If I have a number 1000:

  1. I first put commas (In English Standard) that makes it: 1,000
  2. I split the above at the first comma: I get 1
  3. I convert 1 into words: I get One, name it replaceNumber
  4. I convert the original number into words:

    ==> One Thousand

  5. I convert the first half of the number back to digits with a simple string replace:

    originalNum.replace(replaceNumber, "");
    

The desired result is: For any number convert the first half of the number (on the left before the comma) into numbers and the rest into words.

So for the above example I get 1 thousand

I get 1 and Thousand. I want to know if I can improve it. Please ignore the static keyword; I will remove that in production code. Eclipse has enforced this onto me.

public class FunctionsFun {
    static NumberToWords num;
    public static void main(String[] args){

        String number= "600000";
        int number1 = 600000; 
        num = new NumberToWords(); 


        String numberwithcommas = formatNumbers(number); 

        System.out.println("Number with commas"+numberwithcommas); 

        String numberInWords = num.start(number1);
        System.out.println("Number in words "+numberInWords);


        String originalNum = numberwithcommas;
        String[] parts = originalNum.split(",");
        String part1 = parts[0];
        System.out.println("First Half is "+part1); //This is the top text
        String replace = num.start(Integer.valueOf(part1));
        System.out.println("Before replace "+numberInWords);
        String finnumberInWords = numberInWords.replace(replace, " "); 
        System.out.println(part1+"\n"+finnumberInWords);
    }



    public static String formatNumbers(String input) {

        String number = input;
        double dispString = Double.parseDouble(number);
        DecimalFormat formatter = new DecimalFormat("#,###");
        return (formatter.format(dispString));
    }
}

Here is my number to words class:

public class NumberToWords {

    static public class ScaleUnit {
        private int exponent;
        private String[] names;
        private ScaleUnit(int exponent, String...names) {
            this.exponent = exponent;
            this.names = names;
        }
        public int getExponent() {
            return exponent;
        }
        public String getName(int index) {
            return names[index];
        }
    }

    static private ScaleUnit[] SCALE_UNITS = new ScaleUnit[] {
        new ScaleUnit(63, "vigintillion"),
        new ScaleUnit(60, "novemdecillion"),
        new ScaleUnit(57, "octodecillion"),
        new ScaleUnit(54, "septendecillion"),
        new ScaleUnit(51, "sexdecillion"),
        new ScaleUnit(48, "quindecillion"),
        new ScaleUnit(45, "quattuordecillion"),
        new ScaleUnit(42, "tredecillion"),
        new ScaleUnit(39, "duodecillion"),
        new ScaleUnit(36, "undecillion"),
        new ScaleUnit(33, "decillion"),
        new ScaleUnit(30, "nonillion"),
        new ScaleUnit(27, "octillion"),
        new ScaleUnit(24, "septillion"),
        new ScaleUnit(21, "sextillion"),
        new ScaleUnit(18, "quintillion"),
        new ScaleUnit(15, "quadrillion"),
        new ScaleUnit(12, "trillion"),
        new ScaleUnit(9, "billion"),
        new ScaleUnit(6, "million"),
        new ScaleUnit(3, "thousand"),
        new ScaleUnit(2, "hundred"),

        new ScaleUnit(-1, "tenth"),
        new ScaleUnit(-2, "hundredth"),
        new ScaleUnit(-3, "thousandth"),
        new ScaleUnit(-4, "ten-thousandth"),
        new ScaleUnit(-5, "hundred-thousandth"),
        new ScaleUnit(-6, "millionth"),
        new ScaleUnit(-7, "ten-millionth"),
        new ScaleUnit(-8, "hundred-millionth"),
        new ScaleUnit(-9, "billionth"),
        new ScaleUnit(-10, "ten-billionth"),
        new ScaleUnit(-11, "hundred-billionth"),
        new ScaleUnit(-12, "trillionth"),
        new ScaleUnit(-13, "ten-trillionth"),
        new ScaleUnit(-14, "hundred-trillionth"),
        new ScaleUnit(-15, "quadrillionth"),
        new ScaleUnit(-16, "ten-quadrillionth"),
        new ScaleUnit(-17, "hundred-quadrillionth"),
        new ScaleUnit(-18, "quintillionth"),
        new ScaleUnit(-19, "ten-quintillionth"),
        new ScaleUnit(-20, "hundred-quintillionth"),
        new ScaleUnit(-21, "sextillionth"),
        new ScaleUnit(-22, "ten-sextillionth"),
        new ScaleUnit(-23, "hundred-sextillionth"),
        new ScaleUnit(-24, "septillionth"),
        new ScaleUnit(-25, "ten-septillionth"),
        new ScaleUnit(-26, "hundred-septillionth"),
    };

    static public enum Scale {
        SHORT,
        LONG;

        public String getName(int exponent) {
            for (ScaleUnit unit : SCALE_UNITS) {
                if (unit.getExponent() == exponent) {
                    return unit.getName(this.ordinal());
                }
            }
            return ""; 
        }
    }

    /**
     * Change this scale to support American and modern British value (short scale)
     * or Traditional British value (long scale)
     */
    static public Scale SCALE = Scale.SHORT; 


    static abstract public class AbstractProcessor {

        static protected final String SEPARATOR = " ";
        static protected final int NO_VALUE = -1;

        protected List<Integer> getDigits(long value) {
            ArrayList<Integer> digits = new ArrayList<Integer>();
            if (value == 0) {
                digits.add(0);
            } else {
                while (value > 0) {
                    digits.add(0, (int) value % 10);
                    value /= 10;
                }
            }
            return digits;
        }

        public String getName(long value) {
            return getName(Long.toString(value));
        }

        public String getName(double value) {
            return getName(Double.toString(value));
        }

        abstract public String getName(String value);
    }

    static public class UnitProcessor extends AbstractProcessor {

        static private final String[] TOKENS = new String[] {
            "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
            "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
        };

        @Override
        public String getName(String value) {
            StringBuilder buffer = new StringBuilder();

            int offset = NO_VALUE;
            int number;
            if (value.length() > 3) {
                number = Integer.valueOf(value.substring(value.length() - 3), 10);
            } else {
                number = Integer.valueOf(value, 10);
            }
            number %= 100;
            if (number < 10) {
                offset = (number % 10) - 1;
                //number /= 10;
            } else if (number < 20) {
                offset = (number % 20) - 1;
                //number /= 100;
            }

            if (offset != NO_VALUE && offset < TOKENS.length) {
                buffer.append(TOKENS[offset]);
            }

            return buffer.toString();
        }

    }

    static public class TensProcessor extends AbstractProcessor {

        static private final String[] TOKENS = new String[] {
            "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
        };

        static private final String UNION_SEPARATOR = "-";

        private UnitProcessor unitProcessor = new UnitProcessor();

        @Override
        public String getName(String value) {
            StringBuilder buffer = new StringBuilder();
            boolean tensFound = false;

            int number;
            if (value.length() > 3) {
                number = Integer.valueOf(value.substring(value.length() - 3), 10);
            } else {
                number = Integer.valueOf(value, 10);
            }
            number %= 100;   // keep only two digits
            if (number >= 20) {
                buffer.append(TOKENS[(number / 10) - 2]);
                number %= 10;
                tensFound = true;
            } else {
                number %= 20;
            }

            if (number != 0) {
                if (tensFound) {
                    buffer.append(UNION_SEPARATOR);
                }
                buffer.append(unitProcessor.getName(number));
            }

            return buffer.toString();
        }
    }

    static public class HundredProcessor extends AbstractProcessor {

        private int EXPONENT = 2;

        private UnitProcessor unitProcessor = new UnitProcessor();
        private TensProcessor tensProcessor = new TensProcessor();

        @Override
        public String getName(String value) {
            StringBuilder buffer = new StringBuilder();

            int number;
            if (value.isEmpty()) {
                number = 0;
            } else if (value.length() > 4) {
                number = Integer.valueOf(value.substring(value.length() - 4), 10);
            } else {
                number = Integer.valueOf(value, 10);
            }
            number %= 1000;  // keep at least three digits

            if (number >= 100) {
                buffer.append(unitProcessor.getName(number / 100));
                buffer.append(SEPARATOR);
                buffer.append(SCALE.getName(EXPONENT));
            }

            String tensName = tensProcessor.getName(number % 100);

            if (!tensName.isEmpty() && (number >= 100)) {
                buffer.append(SEPARATOR);
            }
            buffer.append(tensName);

            return buffer.toString();
        }
    }

    static public class CompositeBigProcessor extends AbstractProcessor {

        private HundredProcessor hundredProcessor = new HundredProcessor();
        private AbstractProcessor lowProcessor;
        private int exponent;

        public CompositeBigProcessor(int exponent) {
            if (exponent <= 3) {
                lowProcessor = hundredProcessor;
            } else {
                lowProcessor = new CompositeBigProcessor(exponent - 3);
            }
            this.exponent = exponent;
        }

        public String getToken() {
            return SCALE.getName(getPartDivider());
        }

        protected AbstractProcessor getHighProcessor() {
            return hundredProcessor;
        }

        protected AbstractProcessor getLowProcessor() {
            return lowProcessor;
        }

        public int getPartDivider() {
            return exponent;
        }

        @Override
        public String getName(String value) {
            StringBuilder buffer = new StringBuilder();

            String high, low;
            if (value.length() < getPartDivider()) {
                high = "";
                low = value;
            } else {
                int index = value.length() - getPartDivider();
                high = value.substring(0, index);
                low = value.substring(index);
            }

            String highName = getHighProcessor().getName(high);
            String lowName = getLowProcessor().getName(low);

            if (!highName.isEmpty()) {
                buffer.append(highName);
                buffer.append(SEPARATOR);
                buffer.append(getToken());

                if (!lowName.isEmpty()) {
                    buffer.append(SEPARATOR);
                }
            }

            if (!lowName.isEmpty()) {
                buffer.append(lowName);
            }

            return buffer.toString();
        }
    }

    static public class DefaultProcessor extends AbstractProcessor {

        static private String MINUS = "minus";
        static private String UNION_AND = "and";

        static private String ZERO_TOKEN = "zero";

        private AbstractProcessor processor = new CompositeBigProcessor(63);

        @Override
        public String getName(String value) {
            boolean negative = false;
            if (value.startsWith("-")) {
                negative = true;
                value = value.substring(1);
            }

            int decimals = value.indexOf(".");
            String decimalValue = null;
            if (0 <= decimals) {
                decimalValue = value.substring(decimals + 1);
                value = value.substring(0, decimals);
            }

            String name = processor.getName(value);

            if (name.isEmpty()) {
                name = ZERO_TOKEN;
            } else if (negative) {
                name = MINUS.concat(SEPARATOR).concat(name); 
            }

            if (!(null == decimalValue || decimalValue.isEmpty())) {
                name = name.concat(SEPARATOR).concat(UNION_AND).concat(SEPARATOR)
                    .concat(processor.getName(decimalValue))
                    .concat(SEPARATOR).concat(SCALE.getName(-decimalValue.length()));
            }

            return name;
        }

    }

    static public AbstractProcessor processor;


    public static String start(int num) {

        processor = new DefaultProcessor();
        int bignum = num;
        return processor.getName(bignum);
        //   return calculate(num); 

    }


    public static String start(long num) {

        processor = new DefaultProcessor();
        long bignum = num;
        return processor.getName(bignum);
        //   return calculate(num); 

    }

}
\$\endgroup\$
  • 1
    \$\begingroup\$ VTC broken because the output you want to get and the actual output do not match \$\endgroup\$ – Pimgd Oct 28 '14 at 9:28
  • \$\begingroup\$ I did not understand that? \$\endgroup\$ – User3 Oct 28 '14 at 10:19
  • \$\begingroup\$ You say "I want 1000 to turn into One Thousand, but it gives me 1 Thousand". That means your code is broken. I'm voting to put this question on hold until you've fixed the code. \$\endgroup\$ – Pimgd Oct 28 '14 at 10:35
  • \$\begingroup\$ Please read the question carefully mate, the result is exactly what I want. The title of the question suggests the same, cheers :) I am upvoting your comment, I like it. Oh sorry I dont have that access :D \$\endgroup\$ – User3 Oct 28 '14 at 10:46
  • 2
    \$\begingroup\$ What do you want the output to be of "456789123"? \$\endgroup\$ – Jeroen Vannevel Oct 28 '14 at 12:31
6
\$\begingroup\$

Your code is generally pretty good. I would change a few things...

First of all...

Your formatting makes the code hard to read. For example:

   System.out.println("Number with commas"+numberwithcommas); 

   String numberInWords = num.start(number1);
   System.out.println("Number in words "+numberInWords);

Should be formatted like:

    System.out.println("Number with commas " + numberwithcommas); 

    String numberInWords = num.start(number1);
    System.out.println("Number in words " + numberInWords);

Also,

String numberwithcommas = formatNumbers(number);

Should be like:

String numberWithCommas = formatNumbers(number);

The last one:

public static String formatNumbers(String input) {

    String number = input;
    double dispString = Double.parseDouble(number);
    DecimalFormat formatter = new DecimalFormat("#,###");
    return (formatter.format(dispString));
}

should be (notice the last line excluding the braces):

public static String formatNumbers(String input) {

    String number = input;
    double dispString = Double.parseDouble(number);
    DecimalFormat formatter = new DecimalFormat("#,###");
    return formatter.format(dispString);
}

In other words, the parentheses is not necessary.

Not a big problem, but it will be if you have other people trying to read your code.

FunctionsFun.java

Let's take a look at the first part...

public class FunctionsFun {  
   static NumberToWords num;  
   public static void main(String[] args){  

       String number= "600000";
       int number1 = 600000; 
       num = new NumberToWords(); 

NumberToWords num; can be put inside the main method without harming anything. This allows you to omit the static.

I don't see the use of theString and int representing the same number. Remove String number = "600000", change int number1 to int number, and replace:

String numberwithcommas = formatNumbers(number);

with

String numberWithCommas = formatNumbers(Integer.toString(number));

You also have the numbers hard-coded. You should ask for input using 'Scanner`, like so:

Scanner sc = new Scanner(System.in);

int number = 0;

for (boolean validInput = false; validInput; ) {
    System.out.print("Number: ");

    String input = sc.next();

    try {
        number = Integer.parseInt(input);
        validInput = true;
    } catch {
        System.out.print("Oops. That's not a number. Try again: ");
    }
}
sc.close();

But of course, if you want the numbers to be hard-coded, then don't do this (but I don't see any reason why not to).

NumberToWords.java

First of all, the inner class:

static public class ScaleUnit {
    private int exponent;
    private String[] names;
    private ScaleUnit(int exponent, String...names) {
        this.exponent = exponent;
        this.names = names;
    }
    public int getExponent() {
        return exponent;
    }
    public String getName(int index) {
        return names[index];
    }
}

names does not have to be an array. There is only one name, so it should be:

private String name;

and the constructor gets to change to:

ScaleUnit(int exponent, String name) {
    this.exponent = exponent;
    this.name = name;
}

The getName() method also changes:

public String getName() {
    return name;
}

which means you have to go through your code and remove all the arguments.

OR

Add a second name to match the long scale.

I recommend the second option and add a input option to ask the user for what scale to use, which changes the main method to:

Scanner sc = new Scanner(System.in);

int number = 0;

for (boolean validInput = false; validInput; ) {
    System.out.print("Number: ");

    String input = sc.next();

    try {
        number = Integer.parseInt(input);
        validInput = true;
    } catch {
        System.out.print("Oops. That's not a number. Try again: ");
    }
}

Scale scale = Scale.SHORT; //default

for (boolean validInput = false; validInput; ) {
    System.out.print("Scale: ");

    String input = sc.next();

    if (input.equalsIgnoreCase("short")) {
        // already short, so nothing to be done
        validInput = true;
    } else if (input.equalsIgnoreCase("long")) {
        scale = Scale.LONG;
        validInput = true;
    }
}
sc.close();

And of course you have to move the enum Scale to a new file, then get the result of the input to connect to the other class (I won't go into that).

I think that's it! Great Job!

\$\endgroup\$
  • \$\begingroup\$ Great!! Let me start implementing this :) \$\endgroup\$ – User3 Dec 9 '14 at 6:14

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.