16
\$\begingroup\$

Challenge

Write a calculator that takes input in a verbal form (as one might speak an equation) and also outputs in a verbal form (as one might speak a number).

Rules

The calculator should be able to:

  • add, subtract, multiply and divide
  • handle operands between negative one million and one million
  • handle outputs between negative one billion and one billion
  • handle decimal points in its input and place them correctly in its output
  • handle decimal output to the hundredths place, rounding where needed

All operations yielding fractional results should round to the nearest hundredths place (identically to the output formatting).

Report with output "E" (for error) when input would cause the program to fail due to being incorrectly formatted or dividing by 0; basically, the program shouldn't crash on bad input, because that would be a lousy calculator.

The calculator is allowed, but not required to report errors when operands or output escape their bounds. This is meant to simplify the problem, but if you disagree with me, feel free to make a calculator capable of correctly handling greater operands and outputs without reporting errors.

Output "E" in the event that an operand for an operation exceeds the bounds defined for operands.

Output "E" in the event that the output exceeds the bounds described for outputs

How the program handles case-sensitivity and whitespace are left up to the golfer, as is the choice of British or American English.1

Programs that bypass the implementation of the calculator by using a language or library that has already implemented the functionality described above will be ineligible for victory.

Winner

The program with the lowest number of characters wins.

Examples

Input: two plus two
Output: four

Input: twenty-one point five minus one point five
Output: twenty

Input: one minus two
Output: negative one

Input: five times five
Output: twenty-five

Input: twenty-five divided by five
Output: five

Input: two plus two minus five times five divided by negative zero point five
Output: ten

Input one million times one thousand
Output: one billion

Input: one million times one thousand plus one
Output: E

Input: two million plus one million
Output: E

Input: one million plus one million plus one million
Output: E

\$\endgroup\$
23
  • 2
    \$\begingroup\$ What's the order of evaluation you're using? Normally, two plus two minus five times five divided by negative zero point five -> 2 + 2 - 5 * 5 / -0.5 -> 54. \$\endgroup\$
    – marinus
    Oct 6, 2013 at 6:56
  • 1
    \$\begingroup\$ @marinus looks like strictly left-to-right. Thanks for noting \$\endgroup\$ Oct 6, 2013 at 6:57
  • 1
    \$\begingroup\$ do we have to fail for one million one on the input or one billion one on the output? Also, does the 1e6 limit or the 1e9 limit apply to intermediate results as well? \$\endgroup\$ Oct 6, 2013 at 6:58
  • 2
    \$\begingroup\$ @JanDvorak I'm going to say it isn't necessary to fail on anything (input, output, intermediary results) so long as you can deliver the correct output; the purpose of the failure was to make it easier on people to be honest. \$\endgroup\$
    – guest
    Oct 6, 2013 at 16:45
  • 1
    \$\begingroup\$ You talk about supporting decimals but you don't properly specify how they are to be handled. What would the correct output be for one hundred divided by three point nought? (Also, why is the output of the final example E rather than three million?) \$\endgroup\$ Oct 6, 2013 at 22:43

3 Answers 3

6
\$\begingroup\$

First of all, this is totally cheaty, and not complete to the specs.

requires the --disable-web-security flag on chrome, +22

Javascript 509 + 22 = 531

x=new XMLHttpRequest;y=Object.keys(x);b=alert;q="querySelectorAll";s="send";x[y[3]]="document";x.open("GET","http://www.wolframalpha.com/input/?i="+escape(prompt()));x[y[10]]=function(c){4===x.readyState&&(w=[].filter.call(x.response[q](".pod h2"),function(a){return"ame:"==a.innerText.slice(-4)})[0].parentElement,(k=w[q]("a")[0])&&"Words only"==k.innerText?(x.open("GET",k.href),x.send()):alert(JSON.parse([].pop.call(x[y[2]][q]("script")).innerHTML.match(/d_0.00\.push\((.+?)\)/)[1]).stringified))};x[s]()

The first off the spec is as well the example output
The input two plus two minus five times five divided by negative zero point five outputs

enter image description here

Any other case should get handled fine (now), this is yet pretty ungolfed as is, I just wanted it to get fixed.

input: two plus two
output: four

input: twenty-one point five minus one point five
output: twenty

input: one minus two
output: negative one

input: five times five
output: twenty-five

input: twenty-five divided by five
output: five

input: two plus two minus five times five divided by negative zero point five
output: fifty-four

input: one million times one thousand
output: one billion

input: one million times one thousand plus one
output: one billion, one

input: two million plus one million:
output: three million

input: one million plus one million plus one million
output: three million

Oh, and if you actually going to test it, it may take a few seconds, as it loads the complete Wolfram Alpha page up to two times.

Well, there might be a lot to enhance though.

\$\endgroup\$
6
  • \$\begingroup\$ I can't deny that this is an amusing answer, though it is too bad it doesn't provide output in the correct format. Note that the last two examples also have incorrect output. Of course, there's also the question of how legitimate this answer is... though I didn't explicitly state that you couldn't query some other pre-existing program, I had meant to convey the idea that the translation from English to number, the calculation, and the translation from number to English, should be implemented by the golfer. It does pass the literal interpretation of the rules though, I'll give it that. :) \$\endgroup\$
    – guest
    Oct 7, 2013 at 17:53
  • \$\begingroup\$ @guest =) I totally, agree with you on that =) It's more of fun answer, beding the rules, than an actual ment codegolf. I'm in the process of fixing the outputs and post a new version. Wolfram Alpha uses images only, took me a while to figure out its stored as a data attribute, b64 encoded. \$\endgroup\$
    – C5H8NNaO4
    Oct 7, 2013 at 18:08
  • \$\begingroup\$ @guest, updated it. I hope the outputs are now okay =) \$\endgroup\$
    – C5H8NNaO4
    Oct 7, 2013 at 18:48
  • \$\begingroup\$ If you do this in Mathematica you can control the output to give the correct number decimal places and so on (which is what N would do for fractions): screenshot \$\endgroup\$
    – user11030
    Jan 23, 2014 at 16:02
  • \$\begingroup\$ +1 for actually being creative with cheating, even though this isn't a popularity-contest. \$\endgroup\$
    – alexia
    Jun 25, 2014 at 13:19
4
\$\begingroup\$

Python, 982

from re import*
S=split
U=sub
a=S(' ',U('_','teen ','zero one two three four five six seven eight nine ten eleven twelve thir_four_fif_six_seven_eigh_nine_')+U('_','ty ','twen_thir_for_fif_six_seven_eigh_nine_'))
b=range(20)+range(20,99,10)
d=dict(zip(a,b))
D=dict(zip(b,a))
p='point'
v='negative'
def f(s):
 s=S('[ -]',s);n=0.;m=10**(p in s and(s.index(p)-len(s)))
 for x in s[::-1]:m*=10*(m<1)+100*('hu'in x)+1e3*('ho'in x)+1e6*('m'in x)or 1;n+=(x in d)and m*d[x]
 return n-2*n*(v in s)
def F(n):
 l=[v]*(n<0);x=abs(n)
 for i in(9,6,3,0):z=int(x/10**i);h=z%100;g=(z>99)*[D[z/100],'hundred']+(h>0)*[h in D and D[h]or D[h-z%10]+'-'+D[z%10]];l+=g and g+[[],['thousand'],['million'],['billion']][i/3];x%=10**i
 l+=[c=='.'and p or D[int(c)]for c in'%.2g'%x][n**2>=1:];return' '.join(l)
c=lambda n,l:c(eval(`n`+l[0]+`f(l[1])`),l[2:])if l else n
i=S(' (?=. )|(?<= .) ',U('di.*?y','/',U('times','*',U('minus','-',U('plus','+',raw_input())))))
try:print F(c(f(i[0]),i[1:]))
except:print'E'

I think it works as it should according to the specs, but there are probably a few more bugs. It might act weirdly for input >= one billion or any unexpected words that it interprets incorrectly.

Here's a slightly more readable version with a few changes:

import re
words = re.split(' ', re.sub('_', 'teen ', 'zero one two three four five six seven eight nine ten eleven twelve thir_four_fif_six_seven_eigh_nine_') + re.sub('_', 'ty ', 'twen_thir_for_fif_six_seven_eigh_nine_'))
values = range(20) + range(20, 99, 10)
d = dict(zip(words, values))
D = dict(zip(values, words))

def str_to_num(s):
    s = re.split('[ -]', s)
    n = 0.0
    multiplier = 10 ** ('point' in s and (s.index('point') - len(s)))

    for word in s[::-1]:
        multiplier *= 10 * (multiplier < 1) + 100 * ('hundred' == word) + 1e3 * ('thousand' == word) + 1e6 * ('million' == word) or 1
        n += (word in d) and multiplier * d[word]

    return n - 2 * n * ('negative' in s)


three_digit_num_to_str = lambda n: (n > 99) * [D[n / 100], 'hundred'] + (n % 100 > 0) * [n % 100 in D and D[n % 100] or D[n % 100 - n % 10] + '-' + D[n % 10]]

def num_to_str(n):
    word_list = ['negative'] * (n < 0)
    x = abs(n)

    for i in (9, 6, 3, 0):
        three_digit_str = three_digit_num_to_str(int(x / 10 ** i))
        if three_digit_str:
            word_list += three_digit_str + [[], ['thousand'], ['million'], ['billion']][i / 3]

        x %= 10 ** i

    word_list += [char == '.' and 'point' or D[int(char)] for char in '%.2g' % x][n ** 2 >= 1:]
    return ' '.join(word_list)

calculate = lambda n, l: calculate(eval(str(n) + l[0] + str(str_to_num(l[1]))), l[2:]) if l else n

i = re.split(' (?=. )|(?<= .) ', re.sub('di.*?y', '/', re.sub('times', '*', re.sub('minus', '-', re.sub('plus', '+', raw_input())))))

try:
    print num_to_str(calculate(str_to_num(i[0]), i[1:]))
except:
    print 'E'
\$\endgroup\$
1
\$\begingroup\$

There we go. Golfing the version before broke it, but now we're back online. I'm positive it can be further golfed. I'll work on it more tomorrow. It was hard enough to get it working properly WITHOUT golfing it, though, and my eyes are tired of staring at it. Haha

Java - 3220

import java.util.*;class a{int D=0,i,l,j;static boolean T=true,F=false;enum O{A("plus"),S("minus"),M("times"),D(""),P("point");String t;O(String u){t=u;}double p(double f,double s){if(this==A)f+=s;if(this==S)f-=s;if(this==M)f*=s;if(this==D)f/=s;return f;}static O f(String s){O r=null;for(O o:values())if(s.equals(o.t))r=o;return r;}}enum N{A("zero",0,F),B("one",1,F),C("two",2,F),D("three",3,F),E("four",4,F),AG("five",5,F),G("six",6,F),H("seven",7,F),I("eight",8,F),J("nine",9,F),K("ten",10,F),L("eleven",11,F),M("twelve",12,F),N("thirteen",13,F),O("fourteen",14,F),P("fifteen",15,F),Q("sixteen",16,F),R("seventeen",17,F),S("eighteen",18,F),AH("nineteen",19,F),U("twenty",20,F),V("thirty",30,F),W("forty",40,F),X("fifty",50,F),Y("sixty",60,F),Z("seventy",70,F),AA("eighty",80,F),AB("ninety",90,F),AC("hundred",100,T),AD("thousand",1000,T),AE("million",1000000,T),AF("billion",1000000000,T);String t;double v;boolean q;N(String u,int w,boolean r){t=u;v=w;q=r;}static N f(String s){N r=null;for(N n:values())if(s.equals(n.t))r=n;return r;}static N f(char s){return d(q(""+s));}static N d(double v){N r=null;for(N n:values())if(v==n.v)r=n;return r;}static String c(double n){return d(n).t;}}public static void main(String[]a){new a();}a(){while(T){try{List p=p(new Scanner(System.in).nextLine()),t=new ArrayList();double d=0;for(j=0;j<p.size();j++){Object o=p.get(j);if(o(o)){if((O)o==O.P){t.add((d(t.get(t.size()-1))+((d=d(p.get(j+1)))<10?d*=100:d<100?d*=10:d)/1000));t.remove(t.size()-2);j++;}else t.add(o);}else {N n=N.d(d(o));if(n!=null&&n.q){t.add((d(o))*d(t.get(t.size()-1)));t.remove(t.size()-2);}else t.add(o);}}double r=d(t.get(0));for(j=1;j<t.size();j++){Object c=t.get(j),l=t.get(j-1);if(o(c))continue;if(c instanceof Double&&l instanceof Double)r+=d(c);else r=((O)t.get(j-1)).p(r,d(t.get(j)));}System.out.println(p(r));}catch(Exception e){System.out.println("E");}}}List p(String s) {List r=new ArrayList();Scanner i=new Scanner(s);while(i.hasNext()){String c=i.next();if(c.equals("divided")){r.add(O.D);i.next();}else if(c.indexOf("-")!=-1){String[] num=c.split("-");r.add(N.f(num[0]).v+N.f(num[1]).v);}else{Object o=N.f(c);r.add(o!=null?((N)o).v:O.f(c));}}return r;}String p(double n){String a=String.valueOf(n),w,d=null,b="";l=a.indexOf(".");if(l!=-1){w=a.substring(0,l);d=a.substring(l+1);}else w=a;if(d.equals("0"))d=null;D=0;while(w.length()%3!=0)w=" "+w;for(i=w.length();i>0;i-=3,D++)b=w(w.substring(i-3,i))+b;return b+d(d);}String w(String w) {if(w==null)return "";w=w.trim();String b="";l=w.length();if(l>1&&w.charAt(l-2)!='0'){if(w.charAt(l-2)=='1')b=N.d(q(w.substring(l-2))).t;else b+=N.d(q(w.charAt(l-2)+"0")).t+"-"+N.f(w.charAt(l-1)).t;}for(j=(b.equals("")?l-1:l-3);j>-1;j--){N n=N.f(w.charAt(j));if(n==N.A)continue;if(j==l-1)b=n.t;else if(j==l-2)b=N.f(n.t+"0")+"-"+b;else if(j==l-3)b=n.t+" hundred "+b;}if(!b.trim().equals("")){if(D==1)b+=" thousand ";if(D==2)b+=" million ";if(D==3)b+=" billion ";}return b;}String d(String d) {if(d==null)return"";if(d.length()>3)d=d.substring(0,3);String b = " point ";for(char n:d.toCharArray())b+=N.f(n).t+" ";return b;}boolean o(Object o){return o instanceof O;}Double d(Object o){return (Double)o;}static double q(String s){return Double.parseDouble(s);}}

With line breaks and tabs

import java.util.*;

class a{

    int D=0,i,l,j;
    static boolean T=true,F=false;

    enum O{
        A("plus"),
        S("minus"),
        M("times"),
        D(""),
        P("point");

        String t;       
        O(String u){
            t=u;
        }

        double p(double f,double s){
            if(this==A)f+=s;
            if(this==S)f-=s;
            if(this==M)f*=s;
            if(this==D)f/=s;
            return f;
        }

        static O f(String s){
            O r=null;
            for(O o:values())if(s.equals(o.t))r=o;
            return r;
        }
    }

    enum N{
        A("zero",0,F),
        B("one",1,F),
        C("two",2,F),
        D("three",3,F),
        E("four",4,F),
        AG("five",5,F),
        G("six",6,F),
        H("seven",7,F),
        I("eight",8,F),
        J("nine",9,F),
        K("ten",10,F),
        L("eleven",11,F),
        M("twelve",12,F),
        N("thirteen",13,F),
        O("fourteen",14,F),
        P("fifteen",15,F),
        Q("sixteen",16,F),
        R("seventeen",17,F),
        S("eighteen",18,F),
        AH("nineteen",19,F),
        U("twenty",20,F),
        V("thirty",30,F),
        W("forty",40,F),
        X("fifty",50,F),
        Y("sixty",60,F),
        Z("seventy",70,F),
        AA("eighty",80,F),
        AB("ninety",90,F),
        AC("hundred",100,T),
        AD("thousand",1000,T),
        AE("million",1000000,T),
        AF("billion",1000000000,T);

        String t;
        double v;
        boolean q;

        N(String u,int w,boolean r){
            t=u;
            v=w;
            q=r;
        }

        static N f(String s){
            N r=null;
            for(N n:values())if(s.equals(n.t))r=n;
            return r;
        }

        static N f(char s){
            return d(q(""+s));
        }

        static N d(double v){
            N r=null;
            for(N n:values())if(v==n.v)r=n;
            return r;
        }

        static String c(double n){
            return d(n).t;
        }

    }


    public static void main(String[]a){
        new a();
    }


    a(){
        while(T){
            try{
                List p=p(new Scanner(System.in).nextLine()),t=new ArrayList();
                double d=0;
                for(j=0;j<p.size();j++){
                    Object o=p.get(j);
                    if(o(o)){
                        if((O)o==O.P){
                            t.add((d(t.get(t.size()-1))+((d=d(p.get(j+1)))<10?d*=100:d<100?d*=10:d)/1000));
                            t.remove(t.size()-2);
                            j++;
                        }
                        else t.add(o);
                    }
                    else {
                        N n=N.d(d(o));
                        if(n!=null&&n.q){
                            t.add((d(o))*d(t.get(t.size()-1)));
                            t.remove(t.size()-2);
                        }
                        else t.add(o);
                    }
                }

                double r=d(t.get(0));
                for(j=1;j<t.size();j++){
                    Object c=t.get(j),l=t.get(j-1);
                    if(o(c))continue;
                    if(c instanceof Double&&l instanceof Double)r+=d(c);
                    else r=((O)t.get(j-1)).p(r,d(t.get(j)));
                }

                System.out.println(p(r));
            }
            catch(Exception e){
                System.out.println("E");
            }
        }
    }

    List p(String s) {
        List r=new ArrayList();
        Scanner i=new Scanner(s);
        while(i.hasNext()){
            String c=i.next();
            if(c.equals("divided")){
                r.add(O.D);
                i.next();
            }
            else if(c.indexOf("-")!=-1){
                String[] num=c.split("-");
                r.add(N.f(num[0]).v+N.f(num[1]).v);
            }
            else{
                Object o=N.f(c);
                r.add(o!=null?((N)o).v:O.f(c));
            }
        }
        return r;
    }

    String p(double n){

        String a=String.valueOf(n),w,d=null,b="";

        l=a.indexOf(".");
        if(l!=-1){
            w=a.substring(0,l);
            d=a.substring(l+1);
        }
        else w=a;

        if(d.equals("0"))d=null;

        D=0;
        while(w.length()%3!=0)w=" "+w;

        for(i=w.length();i>0;i-=3,D++)b=w(w.substring(i-3,i))+b;

        return b+d(d);
    }


    String w(String w) {
        if(w==null)return "";
        w=w.trim();

        String b="";
        l=w.length();

        if(l>1&&w.charAt(l-2)!='0'){
            if(w.charAt(l-2)=='1')b=N.d(q(w.substring(l-2))).t;
            else b+=N.d(q(w.charAt(l-2)+"0")).t+"-"+N.f(w.charAt(l-1)).t;
        }

        for(j=(b.equals("")?l-1:l-3);j>-1;j--){
            N n=N.f(w.charAt(j));
            if(n==N.A)continue;
            if(j==l-1)b=n.t;
            else if(j==l-2)b=N.f(n.t+"0")+"-"+b;
            else if(j==l-3)b=n.t+" hundred "+b;
        }

        if(!b.trim().equals("")){
            if(D==1)b+=" thousand ";
            if(D==2)b+=" million ";
            if(D==3)b+=" billion ";
        }

        return b;
    }


    String d(String d) {
        if(d==null)return"";
        if(d.length()>3)d=d.substring(0,3);

        String b = " point ";
        for(char n:d.toCharArray())b+=N.f(n).t+" ";

        return b;
    }

    boolean o(Object o){
        return o instanceof O;
    }

    Double d(Object o){
        return (Double)o;
    }

    static double q(String s){
        return Double.parseDouble(s);
    }

}
\$\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.