lambda T:B("([+/*-])",lambda m:dict(zip("+/*-",S("z"," plus z divided by z times z minus ")))[m.group(0)],B("([+/*-]|^)-",r"\1negative ",B("[^+/*-]+","{}",T))).format(*[J([g[int(S("\.",j)[0])]+S("z",B("y","illion","z thousandz myz byz tryz quadry"))[len(S(",",m))+~i]+(" point "+J(s[int(c)]for c in S("\.",j)[-1]))*("."in j)for i,j in E(S(",",m))if 0<float(j)+(m<"1")])for m in S("[+/*-]+",T)[T[0]=='-':]])
from re import*
E,S,B,P=enumerate,split,sub," ";J=P.join
s,e=S(P,"zero one two three four five six seven eight nine"),[B("urty","rty",j)for i,j in E(c+d for d in S(P,"teen ty")for c in S(P,"twen thir four fif six seven eigh nine"))]
g=s+S(P,"ten eleven twelve")+e[1:8]+[a+(P+b)*(i>0)for a in e[8:]for i,b in E(s)]
g=[(j+" hundred ")*(i>0)+k for i,j in E(s)for k in g]
Try it online!
So many bad practices. This almost hurt to write....
Expects a non-unicode string with no whitespace as input.
Explanation:
# import all functions from re (python regex library)
from re import*
# rename some repeatedly-used functions/variables for reduced bytecount
E,S,B,P=enumerate,split,sub," ";J=P.join
# list the names of 0-9
s=S(P,"zero one two three four five six seven eight nine")
# generate "twenteen" through nineteen and twenty though ninety, changing "fourty" to forty
# using enumerate (E) even though i is not required b/c it's shorter than range(len(x))
# using re.split (S) instead of string.split since it's shorter
e=[B("urty","rty",j)for i,j in E(c+d for d in S(P,"teen ty")for c in S(P,"twen thir four fif six seven eigh nine"))]
# generate 0-999
# 0-9
g=s+
# 10, 11, 12
+S(P,"ten eleven twelve")+
# remove "twenteen", 13-19
+e[1:8]+
# tens' place + ones' place, if ones' place is not zero
+[a+(P+b)*(i>0) ]
# for each tens' place in 20-90
for a in e[8:]
# for each index, value in ones' places 0-9
for i,b in E(s)
# hundreds' place if at least 100, plus tens' and ones' place (already calculated and stored in g from before)
g=[(j+" hundred ")*(i>0)+k ]
# (s) stores names for 0-9, need index to avoid "zero hundred"
for i,j in E(s)
# for each hundred, iterate over all values (0-99) already in g
for k in g
# actual function to call. uses previously declared global variable g.
def f(T):
# gets the numbers in the supplied string (T) by splitting (T) on any operator character
# remove first item if blank (only happens when staring with a - for negative numbers)
n=S("[+/*-]+",T)[T[0]=='-':]
# triply-nested set of re.subs to convert (T) to a sting of where the operators are replaced by their names and numbers are replaced by "{}"
# EX: "-1-1--1" -> "-{}-{}--{}" -> "negative {}-{}-negative {}" -> "negative {} minus {} minus negative {}"
# this sub happens last
# re.sub (B) any operator, with the operators in a group "()" so that they return in match.group
T=B("([+/*-])", )
# an anonymous function to accept match objects (m) from re.sub's search.
,lambda m:
# create a dictionary from the combination of operators and their names
# like {"+":" plus ",...}
# operator names are surrounded by spaces since number names are NOT
dict(zip("+/*-",S("z"," plus z divided by z times z minus ")))
# from the constructed dictionary, select the operator matched by re.sub's search and return it for replacement
[m.group(0)],
# this substitution is second
# re.sub (B) any operator followed by a minus (-), OR a minus at the beginning of the string
# operators/start are grouped, trailing minus is not
,B("([+/*-]|^)-", )
# replace match with the grouped items plus the word "negative"
# EX: "-1-1--1" -> "-{}-{}--{}" -> "negative {}-{}-negative {}"
,r"\1negative ",
# this substitution is done first
# replace any sequence of NON-operators with "{}"
# this removes numbers so the names can be inserted later
# EX: "-1-1--1" -> "-{}-{}--{}"
,B("[^+/*-]+","{}",T))
# technically the previous construction of (T) and (n) can be placed here to save 5 bytes but my poor eyes can't handle that.
# insert constructed names back into original string.
# EX: "-1-1--1" -> "negative {} minus {} minus negative {}" -> "negative one minus one minus negative one"
print T.format( )
# string.format needs items in array unpacked, or it will attempt to insert the string representation of the array itself
*[ ]
# for each number pulled from (T), generate names and join generated items back together with spaces
# EX: "1,456" -> ["1", "456"] -> ["one thousand", "four hundred fifty six"] -> "one thousand four hundred fifty six"
J( )for m in n
# split j on periods (.) and take the first item
# convert that item into an integer and find the item at that index in g (0-999)
[g[int(S("\.",j)[0])]+ ]
# insert prefix for millions +, split string on "z" (spaces must be preserved for proper separation)
+S("z",B("y","illion","z thousandz myz byz tryz quadry"))
# left is largest, so take the item at index (total # of groups - current place - 1)
[len(S(",",m))+~i]+
# if group had a period, split string on period and take last item
# replace every character in group with number 0-9 name
# join them with spaces and add back to rest of group
+(" point "+J(s[int(c)]for c in S("\.",j)[-1]))*("."in j)
# split number into groups by comma
# EX: "123,456" -> ["123","456"]
# only return item if j != 0 (avoids returning empty string which will result in too many joined spaces)
# OR if m == 0 (avoids not returning anything when should return "zero")
for i,j in E(S(",",m))if 0
I reduced by about 150 bytes while writing the explanation. Let it never be said that commenting/reviewing your code isn't helpful!
123,456,789,012,345.6789
to the examples? It should cover a lot of test cases. \$\endgroup\$minus
instead ofnegative
? \$\endgroup\$/
isover
and negative number isminus
, so it needs some manipulation. \$\endgroup\$