Worded Calculator

A simplistic version of English numeral calculator

Write a program that takes a string as input and outputs the result of the expression.

Rules

The input string will be worded and not numeral.

There will be no parentheses.

Order of calculation will be divide,multiply,subtract then add.

For same operations, the calculations must be done from left to right.

All input numbers will be integers from -999 to 999 (both inclusive)

Output will be an integer of any range.

Division will always be perfectly divisible and zero will never be a denominator.

Restriction of case for input is optional. You don't have to check for the validity of the input.

Number Format

0 to 20 -> zero,one,two...nineteen,twenty
21 to 99 -> twenty one,twenty two.....ninety eight,ninety nine
100 to 999 -> one hundred, one hundred one, one hundred two...one hundred ten....two hundred fifty....nine hundred ninety eight,nine hundred ninety nine


For negative numbers: Add minus to its positive equivalent

Operation Format

Addition: one plus two
Subtraction: one minus two
Multiplication: one time two #Note that for one on the left of multiplication, it is one time and not times.
two times one hundred
Division: forty divided by two


Examples:

o/p <- input

20     four times five
35     twenty plus fifteen
70     fifty plus five times four
-90    minus one time ninety
25     twenty one minus minus four
45     ninety divided by two
700    one time seven hundred
555    one hundred eleven times two plus three hundred thirty three
99     one hundred plus minus one
45     forty five plus two hundred times zero
4     four
-3     three minus three minus three


This is code-golf so shortest code wins

• Duplicate? - I think that's close enough to be considered as such. Commented Nov 6, 2018 at 8:45
• It's very close indeed. But I think this one is better specified and has more reasonable limitations. Commented Nov 6, 2018 at 8:53
• @Arnauld I will keep this open then but if others think differently, just mark it as duplicate. Commented Nov 6, 2018 at 8:56
• I say one times two. Is using time normal?
– Jo King
Commented Nov 6, 2018 at 9:46
• I think you meant 'one times seven hundred'? Commented Nov 6, 2018 at 17:16

JavaScript (ES6), 257252249 235 bytes

Saved 3 bytes thanks to @Shaggy

s=>eval(s.split .map(w=>(i='zeonwohrr44fx3n5t54nn3leel8tou7fn7n98etetwthfofisiseeinihuplmitidiby'.match(/../g).findIndex(x=>~(w+w.length+w).search(x)))>28?n+' '+'+-*/ '[n='',i-29]:(n=+n+(i<28?i<20?i:i*10-180:n*99),''),n='').join+n)


Try it online!

How?

Once the input string has been split into words, each word $$\W\$$ of length $$\L\$$ is converted to an index $$\i\$$ by looking for the first 2-character key substring that can be found in $$\W+L+W\$$.

Numbers

 index | word              | word + length + word         | key substring
-------+-------------------+------------------------------+---------------
0  | "zero"            | "zero4zero"                  | "ze"
1  | "one"             | "one3one"                    | "on"
2  | "two"             | "two3two"                    | "wo"
3  | "three"           | "three5three"                | "hr"
4  | "four"            | "four4four"                  | "r4"
5  | "five"            | "five4five"                  | "4f"
6  | "six"             | "six3six"                    | "x3"
7  | "seven"           | "seven5seven"                | "n5"
8  | "eight"           | "eight5eight"                | "t5"
9  | "nine"            | "nine4nine"                  | "4n"
10  | "ten"             | "ten3ten"                    | "n3"
11  | "eleven"          | "eleven6eleven"              | "le"
12  | "twelve"          | "twelve6twelve"              | "el"
13  | "thirteen"        | "thirteen8thirteen"          | "8t"
14  | "fourteen"        | "fourteen8fourteen"          | "ou"
15  | "fifteen"         | "fifteen7fifteen"            | "7f"
16  | "sixteen"         | "sixteen7sixteen"            | "n7"
17  | "seventeen"       | "seventeen9seventeen"        | "n9"
18  | "eighteen"        | "eighteen8eighteen"          | "8e"
19  | "nineteen"        | "nineteen8nineteen"          | "te"
20  | "twenty"          | "twenty6twenty"              | "tw"
21  | "thirty"          | "thirty6thirty"              | "th"
22  | "forty"           | "forty5forty"                | "fo"
23  | "fifty"           | "fifty5fifty"                | "fi"
24  | "sixty"           | "sixty5sixty"                | "si"
25  | "seventy"         | "seventy7seventy"            | "se"
26  | "eighty"          | "eighty6eighty"              | "ei"
27  | "ninety"          | "ninety6ninety"              | "ni"
28  | "hundred"         | "hundred7hundred"            | "hu"


Operators

 index | word              | word + length + word         | key substring
-------+-------------------+------------------------------+---------------
29  | "plus"            | "plus4plus"                  | "pl"
30  | "minus"           | "minus5minus"                | "mi"
31  | "times" or "time" | "times5times" or "time4time" | "ti"
32  | "divided"         | "divided7divided"            | "di"
33  | "by"              | "by2by"                      | "by"


Interpretation

The current number is stored in $$\n\$$, which is initialized to an empty string. Given the index $$\i\$$ of the next word, the following logic applies:

i > 28 ?                  // if the word is an operator:
n +                     //   append n (which is either an empty string or a number)
' ' +                   //   append a space
'+-*/ '[n = '', i - 29] //   reset n to an empty string and append the operator
//   the useless keyword 'by' is translated into a harmless space
: (                       // else:
n =                   //   update n:
+n + (              //     force the coercion of the current value of n to a number
i < 28 ?          //     if the word is not 'hundred':
i < 20 ?        //       if the value of the word is less than 'twenty':
i             //         add i
:               //       else:
i * 10 - 180  //         add i * 10 - 180 (e.g. 'fifty' -> 23 * 10 - 180 = 50)
:                 //     else:
n * 99          //       multiply n by 100 by adding 99 * n to itself
),                  //
''                    //   remove this word from the original string
)                       //


Perl 6, 170 139 129 128 124 122 bytes

-13 bytes thanks to nwellnhof!

{S:g/(<:N>+)+%\s/({'+'X$0})/.EVAL}o{S:g/" ҈"/00/}o{TR"⁢ŉ؊⟠"*/൰ "}o*.words>>.&{chr first *.uniname.comb(.uc),1..*}  Try it online! unival to the rescue again! This is (currently) even beating golfing languages like 05AB1E! Explanation: *.words # Split by word boundaries (in this case spaces) >>.{ } # Map each word to chr first ,1..* # The first character where: *.uniname # The unicode name of that character # e.g. DIGIT FIVE .comb(.uc) # Contains the uppercase of the word { }o # Pass this list to another function # That converts the list to a string TR"⁢ŉ؊⟠"*/൰ " #"# And parse out the garbage characters that were wrong # INVISIBLE TIMES => "*" # LOZENGE DIVIDED BY HORIZONTAL RULE => "/" # ARABIC-INDIC PER TEN THOUSAND SIGN => "൰" (value ten) # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE => " " {S:g/" ҈"/00/}o # Replace the character for "hundred" with 00 { }o # And finally combine with S:g/(<:N>+)+%\s/ # Substitute each number-like character separated by spaces /({'+'X$0})/   # With the numbers prefixed by '+'s, in brackets
# This works because Perl 6 supports numeric unicode literals, like
# ፳ => 20, ፴ => 30, ፺ => 90, etc.
.EVAL   # And finally evaluate the whole expression


Python 2, 333...284277 275 bytes

lambda s:eval(''.join((list('+-/*')+[N(w,0)*100+N(w,2)])['pmdt'.find(w)]for w in re.split(' *(?:(p|m)|(t|d)i|by).*? ',s)if w))
N=lambda x,y:sum(10*(w[-3:]in'lveen')+'zeontwthfofisiseeiniteel'.find(w[:2])/2*10**('y'in w)for w in x.rpartition('hundred')[y].split())
import re


Try it online!

Wolfram Language 95 94 82 bytes

Interpreter["SemanticExpression"][#~StringReplace~{"me "->"mes ","plus m"->"m"}]&


# represents the input for the pure function.

If necessary,StringReplace replaces "time " with times ", "plus minus" with "minus" (through "me "->"mes ","plus m"->"m", respectively). The shortened replacement forms, suggested by lirtosiast, saved 12 bytes.

Interpreter["SemanticExpression"] does all the rest.

• Can you change "time "->"times " to "me"->"mes" and "plus minus"->"minus" to "plus m"->"m"? Commented Nov 15, 2018 at 14:03
• Yes. Excellent suggestions. Commented Nov 15, 2018 at 16:50

05AB1E, 166147141139 135 bytes

„byK“¡×€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š—¿áÓÁÏ“#vyN:}'tK'…§¦'+T«:'°¡„ *т«©:.•1×j›o!ĆÖ•3ôŽ9oS:'y®¨:©“‰´Øè„Æ€ººß“'tK#UX¡εð¡õK2ôJ.EO}®áX"+-**/"S:Sðì.ιJ.E


Way too long.. Will try to golf it down from here.

-4 bytes thanks to @Emigna.
-2 bytes thanks to @JoKing.

Explanation:

„byK                 # Remove "by" from the (implicit) input-string
“¡×€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š—¿áÓÁÏ“
# Push string "zero one two three four five six seven eight nine ten eleven twelve"
#                   # Split by spaces
v   }              # Loop over each of these items:
yN:               #  Replace the item with its 0-indexed index
'tK                 '# Remove all "t"
'…§¦                '# Push string "teen", and remove the first character: "een"
'+              '# Push string "+"
T«             # Append 10: "+10"
:            # Replace all "een" with "+10"
'°¡                 '# Push string "hundred"
„ *               # Push string " *"
т«             # Append 100: " *100"
©            # Store it in the register (without popping)
:           # Replace all "hundred" with " *100"
.•4º»Ÿć'Rþн•        '# Push string "wenhirforfif"
3ô       # Split the string into parts of size 3: ["wen","hir","for","fif"]
Ž9o    # Push integer 2345
S   # Split to digits: [2,3,4,5]
:  # Replace each
'y                  '# Push string "y"
®                  # Push the " *100" from the register
¨                 # Remove the last character: " *10"
:                # Replace all "y" with " *10"
©                    # Save the current string in the register (without popping)
“‰´Øè„Æ€ººß“        # Push string "plus minus times time divided"
'tK    '# Remove all "t": "plus minus imes ime divided"
#    # Split by spaces: ["plus","minus","imes","ime","divided"]
U   # Pop and save it in variable X
X  # And push variable X back again
¡ # Split the string by those operator-strings
ε          }         # Map each substring to:
ð¡                  #  Split by spaces (NOTE: cannot be #; if the string contains no
#   spaces, # remains string, whereas ð¡ wraps it in a list)
õK                #  Remove empty strings from the list
2ô              #  Split the list into parts of two
J             #  Join each pair together
.E           #  Evaluate each as a Python eval expression
O          #  Sum them
®                    # Put the string from the register to the stack again
á                   # Remove everything except for letters
X                  # Push variable X: ["plus","minus","imes","ime","divided"]
"+-**/"           # Push string "+-**/"
S          # Split to characters: ["+","-","*","*","/"]
:         # Replace each
S                    # Split the string of operators to loose characters
ðì                  # Prepend a space before each
.ι                # Interweave all sums with these operator-characters
J               # Join everything together to a single string
.E                   # Evaluate each as a Python eval expression (and output implicitly)


See this 05AB1E tip of mine (sections How to use the dictionary?, How to compress strings not part of the dictionary?, and How to compress large integers?) to understand how “¡×€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š—¿áÓÁÏ“, '…§, '°¡, .•4º»Ÿć'Rþн•, Ž9o, and “‰´Øè„Æ€ººß“ work.

Step by step example:

• Input: two hundred twenty two divided by two times minus fifty seven plus three hundred eighteen minus minus ten
• Step 1: Remove "by": two hundred twenty two divided two times minus fifty seven plus three hundred eighteen minus minus ten
• Step 2: Convert "zero" through "twelve" with the correct number: 2 hundred twenty 2 divided 2 times minus fifty 7 plus 3 hundred 8een minus minus 10
• Step 3: Remove all "t": 2 hundred weny 2 divided 2 imes minus fify 7 plus 3 hundred 8een minus minus 10
• Step 4: Replace all "een" with "+10": 2 hundred weny 2 divided 2 imes minus fify 7 plus 3 hundred 8+10 minus minus 10
• Step 5: Replace all "hundred" with " *100": 2 *100 weny 2 divided 2 imes minus fify 7 plus 3 *100 8+10 minus minus 10
• Step 6: Replace all ["wen","hir","for","fif"] with the correct digit: 2 *100 2y 2 divided 2 imes minus 5y 7 plus 3 *100 8+10 minus minus 10
• Step 7: Replace all "y" with " *10": 2 *100 2 *10 2 divided 2 imes minus 5 *10 7 plus 3 *100 8+10 minus minus 10
• Step 8: Split by ["plus","minus","ime","imes","divided"]: ["2 *100 2 *10 2 "," 2 "," "," 5 *10 7 "," 3 *100 8+10 "," "," 10"]
• Step 9: Split each on spaces: [["2","","*100","2","*10","2",""],["","","2",""],["",""],["","5","*10","7",""],["","3","","*100","8+10",""],["",""],["","10"]]
• Step 10: Remove empty items: [["2","*100","2","*10","2"],["2"],[],["5","*10","7"],["3","*100","8+10"],[],["10"]]
• Step 11: Split into parts of size 2 and join: [["2*100","2*10","2"],["2"],"",["5*10","7"],["3*100","8+10"],"",["10"]]
• Step 12: Python eval each: [[200,20,2],[2],"",[50,7],[300,18],"",[10]]
• Step 13: Sum each: [222,2,"",57,318,"",10]
• Step 14: Re-push the string from the register, and remove everything except letters: dividedimesminusplusminusminus
• Step 15: Replace "plus","minus","imes","ime","divided" with the operator characters, and prepend them with a space: [" /"," *"," -"," +"," -"," -"]
• Step 16: Interweave and join both together: 222 /2 * -57 +318 - -10
• Output: Python eval the string, and output implicitly: -5999.0
• Haven't tried to make a solution from scratch or studied yours in detail, but I noticed one golf. Commented Nov 6, 2018 at 12:33

sfk, 572449 423 bytes

This could all be one line, but for the purposes of reading it I used newlines instead of spaces.

xed -i
_plus_+_
_minus_-_
_times[ortext]time_*_
_divided?by_/_
+xed
"_[white][2 chars of a-z][chars of a-z]ty_[parts 1,2]0_"
"_[white][2 chars of a-z][chars of a-z]een_[part1]1[part2]_"
_ten_10_
_lev_11_
_twe_12_
+xed
_ze_0_
_on_1_
_tw_2_
_th_3_
_fo_4_
_fi_5_
_si_6_
_se_7_
_ei_8_
_ni_9_
+xed
_0[white][keep][digit]__
"_[chars of e-z ]__"
+xed
"_?dd[keep][2 digits]_[part1]_"
_?dd[keep][digit]_[part1]0_
_dd_00_
+calc #text


Try it online!