7
\$\begingroup\$

This converts from a number to a Spanish word for example :

100 => cien

200 => doscientos

I used a divide-and-conquer approach:

Constants:

package numberTranslator;

import java.util.Arrays;
import java.util.Comparator;


public enum ExSpanishSufix {


    ciento("s",2), 
    mil("",3),
    millon("es",6), 
    billon("es",9),
    trillon("es",12),
    quadrillion("es",15);


    private String plural;
    private int exponent;
    private long value;

    private ExSpanishSufix(String plural, int exponent){

        this.plural= plural;
        this.exponent=exponent;
        this.value=(long)Math.pow(10, exponent);

    } 

    public static ExSpanishSufix[] getSorted(){

        ExSpanishSufix[] values= ExSpanishSufix.values();
        Arrays.sort(values, 
          Comparator.comparing((ExSpanishSufix hex) -> hex.getExponent()));

        return values;
    }

    public String isPlural(long number){

        return this.name()+(number>1?plural:"");
    }

    public int getExponent(){

        return exponent;
    }

    public long getValue(){

        return value;
    }


}

package numberTranslator;

import java.util.Arrays;
import java.util.Comparator;

public enum SpanishDigits {

    cero(0), uno(1),dos(2),tres(3),cuatro(4),cinco(5),seis(6),siete(7),ocho(8),
    nueve(9), dies(10), once(11), doce(12), trece(13), catorce(14), quince(15);

    private int number;

    private SpanishDigits(int pnumber){
        number=pnumber;
    }

    public static SpanishDigits[] getSorted(){

        SpanishDigits[] values= SpanishDigits.values();
        Arrays.sort(values, 
            Comparator.comparing((SpanishDigits hex) -> hex.getNumber()));
        return values;
    }


    public int getNumber(){

        return number;
    }

}

package numberTranslator;

import java.util.Arrays;
import java.util.Comparator;

public enum SpanishTens {

    dies(10,"dieci"), veinte(20,"veinti"), treinta(30), cuarenta(40), 
    cincuenta(50), sesenta(60), setenta(70), ochenta(80), 
    noventa(90), cien(100);

    private int number;
    private String plural;


    private SpanishTens(int pnumber, String pplural){

        number= pnumber;
        plural=pplural;

    }
    private SpanishTens(int pnumber){

        this(pnumber,"");
    }

    public String isPlural(int pnumber){

        if(pnumber>number && plural!="")
            return plural;

        return name(); 
    }

    public static SpanishTens[] getSorted(){

        SpanishTens[] values= SpanishTens.values();
        Arrays.sort(values, 
            Comparator.comparing((SpanishTens des) -> des.getNumber()));
        return values;
    }

    public int getNumber(){

        return number;
    }

}

Interface

package numberTranslator;

public interface ILangNumber {

    public ILangNumber add(ILangNumber number);
    public ILangNumber multiply(ILangNumber number);
    public ILangNumber divide(ILangNumber number);
    public ILangNumber pow(ILangNumber exponent);
    public long getNumber();

}

package numberTranslator;

public abstract class AbstractLangNumber implements ILangNumber {

    private long number;
    private String parseNumber;

    public AbstractLangNumber(long number){

        this.number=number;
        parseNumber=parseNumber(number);

    } 

    @Override
    public ILangNumber add(ILangNumber number) {

        long add= getNumber()+ number.getNumber(); 
        return createNumber(add);
    }

    @Override
    public ILangNumber multiply(ILangNumber number) {

        long mult= getNumber()*number.getNumber();
        return createNumber(mult);
    }

    @Override
    public ILangNumber divide(ILangNumber number) {

        long divide= getNumber()/number.getNumber();
        return createNumber(divide);
    }

    @Override
    public ILangNumber pow(ILangNumber exponent) {

        long pow= (long)Math.pow(getNumber(), exponent.getNumber());
        return createNumber(pow);
    }

    @Override
    public long getNumber() {
        return number;
    }


    abstract protected String parseNumber(long number);

    abstract protected ILangNumber createNumber(long number);

    @Override
    public String toString(){

        return parseNumber;
    }
}

Implementation

package numberTranslator;

public class SpanishNumber  extends AbstractLangNumber{


    public static SpanishDigits[] digits= SpanishDigits.getSorted();
    public static SpanishTens[] tens= SpanishTens.getSorted();
    public static ExSpanishSufix[] exponents= ExSpanishSufix.getSorted();

    public SpanishNumber(long number) {
        super(number);
    }

    @Override
    protected String parseNumber(long number) {

        if(number == Long.MIN_VALUE)
            throw new IllegalArgumentException();

        StringBuilder sb= new StringBuilder();
        sb.append(getSing(number));
        format(Math.abs(number),sb);  

        return especialCases(sb.toString());
    }

    @Override
    protected ILangNumber createNumber(long number) {
        return new SpanishNumber(number);
    }


    private void format( long number, StringBuilder sb){

        if(number<=100)
            baseCase((int)number, sb);
        else
        {

            ExSpanishSufix suf= findSufix(number);
            long leading= number/suf.getValue();

            if(leading > 1) 
                format(leading, sb);

            sb.append((suf.getExponent() == 2 ? "" : " "))
            .append(suf.isPlural(leading)).append(" ");

            format( number % suf.getValue(), sb);
        }

    }

    private String especialCases(String numero){
        if(numero.isEmpty())
            return "cero";

        return numero.replaceAll("cincocientos", "quinientos")
        .replaceAll("nuevecientos", "novecientos")
        .replaceAll("sietecientos", "setecientos");
    }

    private void baseCase(int magnitud, StringBuilder sb) {

        if(magnitud<16)
            sb.append(magnitud>0?digits[magnitud].name():"");
        else
        {
            sb.append(tens[(magnitud/10)-1].isPlural(magnitud));
            getUnits(magnitud,digits,sb);
        }
    }

    private ExSpanishSufix findSufix(long number)
    {
        long expon= nearestPowerOf10(number);

        int suf=0;

        while(suf<exponents.length-1 && expon>=exponents[suf+1].getExponent())
           ++suf;

        return exponents[suf];

    }

    private String getSing(long numero){

        return numero<0?"menos ":"";
    }

    private void getUnits(int numero, SpanishDigits[] digits, StringBuilder sb){

        int residuo=numero%10;

        if(residuo!=0)
        { 
            String divisor= numero>30?" y ":"";
            sb.append(divisor).append(digits[residuo]);
        }
     }

    private int nearestPowerOf10(long number){

        int n = 0;
        long f[]={10000000000000000L,100000000,10000,100,10};
        for(int i =0, j=16; i<f.length; i++, j/=2)
        { 
            if(number>=f[i])
            { 
                number/=f[i];
                n+=j; 
            }
        }
        return  n;
    }
}

Example:

public static void main(String[] args) 
{
    SpanishNumber p = new SpanishNumber(89799879799L); 
    SpanishNumber p2 = new SpanishNumber(3);
    System.out.println(p.multiply(p2));     
}
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Unless I am reading incorrectly, you should not require any of the getSorted() methods for the three enum types as they are already... sorted.

  • ExSpanishSufix is sorted by exponent, but you have already arranged the values by that in increasing order.
  • SpanishDigits is sorted by number, but you have already arranged the values by that in increasing order.
  • SpanishTens is sorted by number, but you... get the drift.

BTW, you can also rely on the ordinal() method for SpanishDigits to get the numeric values they represent, instead of having the extra field number.

Inside AbstractLangNumber, you can also consider inlining the results:

@Override
public ILangNumber add(ILangNumber number) {
    // just do this
    return createNumber(getNumber() + number.getNumber());
    //long add= getNumber()+ number.getNumber(); 
    //return createNumber(add);
}

Also, your bracing style is quite inconsistent... sometimes you use the Java convention, sometimes you don't, and sometimes you omit them completely. I will suggest sticking to the convention throughout to improve code readability.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ yes the are already sorted I did it on purpose. I thought that if some one changed the enumeration or added new ones could potentially break mi code and since they are actually constants the asymptotic complexity wouldn't really matter, therefore the performance loss would be insignificant. Am I wrong? \$\endgroup\$
    – MAG
    Sep 29, 2015 at 4:19
  • \$\begingroup\$ If someone changed the enumeration, they could change getSorted() as well right, for example by doing a reverse comparison instead? ;) \$\endgroup\$
    – h.j.k.
    Sep 29, 2015 at 4:22
  • \$\begingroup\$ The point is, these kind of changes are better caught through unit testing... an enum type is already immutable (in some sense of that word), so if you have to worry about breaking changes before compilation, then that's what unit testing is (usually) for. \$\endgroup\$
    – h.j.k.
    Sep 29, 2015 at 4:26
  • \$\begingroup\$ Yes I guess you are right but by using getSorted I transmit the intent of my code more clearly. (The enums need to be sorted). While the implementation of getSorted() is pretty clear by itself. I could be wrong though. \$\endgroup\$
    – MAG
    Sep 29, 2015 at 4:26
  • \$\begingroup\$ Wells, if you really prefer to keep the getSorted() method around, you may just want to reference the sorted results in a private static final field as well, so as to avoid the (potentially) repetitive sorting whenever the method is called. However, really, you ain't gonna need it... ;) \$\endgroup\$
    – h.j.k.
    Sep 29, 2015 at 5:47
4
\$\begingroup\$

As Spanish native speaker, for thousands, millions and billions quantities there's a grammatical (Spanish translation) error related to "Uno" numbering. As Follows in the next example:

For the number: 1 481 481 461

This is the translation from the current code:

billon cuatrocientos ochenta y uno millones cuatrocientos ochenta y uno mil cuatrocientos sesenta y uno

the correct way is: billon cuatrocientos ochenta y un millones cuatrocientos ochenta y un mil cuatrocientos sesenta y uno

The same happens with lesser quantities on thousands scale. i.e. 21 0001 "veintiuno mil uno" should be "veintiun mil uno".

The code Fix solution is below replacing the especialCases method implementation :

    private String especialCases(String numero){
        if(numero.isEmpty())
            return "cero";
        
        if(numero.substring(numero.length()-3,numero.length() ).equals("uno") ) {
            numero= numero.replaceAll("uno", "un");
            numero = numero + "o";
            return numero.replaceAll("cincocientos", "quinientos")
                .replaceAll("nuevecientos", "novecientos")
                .replaceAll("sietecientos", "setecientos");
        }
        else {
                numero= numero.replaceAll("uno", "un");
            return numero.replaceAll("cincocientos", "quinientos")
            .replaceAll("nuevecientos", "novecientos")
            .replaceAll("sietecientos", "setecientos");   
        }
    }

Another bug is related with "dies" to represent "ten" based numbers. orthographic error, should be "diez". Below correction for SpanishDigits enum

enum SpanishDigits {
    cero(0), uno(1),dos(2),tres(3),cuatro(4),cinco(5),seis(6),siete(7),ocho(8),
    nueve(9), diez(10), once(11), doce(12), trece(13), catorce(14), quince(15);

    private int number;

    private SpanishDigits(int pnumber){
    number=pnumber;
    }

    public static SpanishDigits[] getSorted(){
    SpanishDigits[] values= SpanishDigits.values();
    Arrays.sort(values, 
    Comparator.comparing((SpanishDigits hex) -> hex.getNumber()));
    
    return values;
    }

    public int getNumber(){
    return number;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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