# Molar masses of compounds

Write a program that takes in a compound made solely of elements with an atomic number less than or equal to 92 (Uranium), and outputs the molar mass of the compound in grams/mole.

## Rules and Restrictions

• You may not use a function that directly calculates molar masses for you.
• Your program must be able to run on an offline machine.
• You MAY use a separate data file. Contents of this file must be provided.
• Your score is the length of your program in bytes, plus the the length of your data file in bytes, should you choose to use one.
• This is , therefore, lowest score wins.

## Input

A string containing the compound. This string may be read through STDIN, passed as an argument, or set to a variable (please specify which). The following are all valid inputs:

• CH2 (one Carbon, two Hydrogens)
• (CH2)8 (8 Carbons, 16 Hydrogens)
• U (1 Uranium)

Your program is not required to function for embedded parentheses (i.e. ((CH3)2N4)4), nor for any compound that contains an element with atomic number greater than 92. If your program does not function for either of the above two cases, it must output Invalid formula for such cases - no undefined behavior.

## Output

You should output, to STDOUT or to a variable, the molar mass of the compound in grams/mole. A list of atomic masses of elements is available here (Wikipedia). Your answer should be accurate to the decimal place for compounds containing fewer than 100 atoms (rounded).

If the input is invalid, Invalid formula must be outputted.

Information on calculating the molar mass of a compound in grams/mole (Wikipedia).

## Examples

Input                   Output
H2O                     18.015
O                       15.999 (16 acceptable)
C6H2(NO2)3CH3           227.132
FOOF                    69.995
C6H12Op3                Invalid formula
Np                      237 (or Invalid formula)
((C)3)4                 144.132 (or Invalid formula)
CodeGolf                Invalid formula

• "to the decimal point" - for how large molecules? The mass of U1000000 is harder to determine to the decimal point than the mass of U2 – John Dvorak Aug 2 '14 at 11:09
• to the decimal point for molecules with less than 100 atoms. Added this to the question statement. – es1024 Aug 2 '14 at 11:16
• I also assume I'm allowed to output 13 if the atomic mass is 12.999? – John Dvorak Aug 2 '14 at 11:16
• That doesn't sound right @es1024. 13, ok, but 12? – RubberDuck Aug 2 '14 at 12:16
• "Your answer should be accurate to the decimal place" does that mean to one decimal place or to the nearest integer? – user16402 Aug 2 '14 at 13:04

# Bash, 978 708 675 673 650 636 632 631 598 594

211 bytes for the program and 382 bytes for the data.

Input is on STDIN, output is on STDOUT. WARNING: creates files called g and a, if they exist, they will be overwritten!

zcat y>g 2>a
s=(sed 's/[A-Z][a-z]*/& /g;q'<g)
p=1.008
v=sed 's/[0-9]\+/*&/g;s/(/+(0/g'
r=(tail -1 g)
for n in {0..91};{
p=bc<<<$p+${r[n]}
v=${v//${s[n]}/+$p} } o=bc<<<0$v 2>a
echo ${o:-Invalid formula}  ## Data file This requires a file called y, which is the zopfli-compressed form of this data (no trailing newline). zopfli is a gzip-compatible compression algorithm and can be decompressed with standard gzip tools. It was run with 1024 iterations (this is probably too much). The last 8 bytes were then removed. HHeLiBeBCNOFNeNaMgAlSiPSClKArCaScTiVCrMnFeNiCoCuZnGaGeAsSeBrKrRbSrYZrNbMoTcRuRhPdAgCdInSnSbITeXeCsBaLaCePrNdPmSmEuGdTbDyHoErTmYbLuHfTaWReOsIrPtAuHgTlPbBiPoAtRnFrRaAcPaThU 0 2.995 2.937 2.072 1.798 1.201 1.996 1.992 2.999 1.182 2.81 1.315 2.677 1.103 2.889 1.086 3.39 3.648 .850 .130 4.878 2.911 3.075 1.054 2.942 .907 2.848 .24 4.613 1.834 4.346 2.904 2.292 4.038 .944 3.894 1.670 2.152 1.286 2.318 1.682 3.054 2.04 3.07 1.836 3.514 1.448 4.543 2.407 3.892 3.050 5.144 .696 3.693 1.612 4.422 1.578 1.211 .792 3.334 .758 5.36 1.604 5.286 1.675 3.575 2.43 2.329 1.675 4.120 1.913 3.523 2.458 2.892 2.367 4.023 1.987 2.867 1.883 3.625 3.788 2.82 1.78 .02 1 12 1 3 1 4.036 1.002 5.991  The base64 of y is (use base64 -d to reproduce the original file): H4sIAAAAAAACAwTB226DMAwA0G9LvEJQIbVi70LfHPBoJAiSaR729zsnBB2LVw/x0UWNMm1up4IE +90ZCC1cvsCm2mkscEJ71l56dRept7ulTDY/Lebp5CW19MLVbbAOlSrlgfVH4fIyCihaXPGg49b6 lfPHXzhvxsecxxZ+Wb6TPq7B8O1a2HjH7Aue7p1qZ0ncgsKvz/8WxuRGoigGgfcfxYvA8r7kn9iA ei6ohAt/+lzuihmD1PFnMrdIV0PeNfOczh3Ylrw8hnHaM6w1WC8V3X4hcYjOfbKlTyz0pewsP5nh plOUK9mkPzkd4HLiCbktIGyQI5uaUvZzNBrwLhOf9hJij+Jo5WBf6mHLfh2OFqeaxOHbaGAZl5mL h5UBI3Hlx99GX4llPumDjgw+NIee7uCaRbrZkzluIhJYi2E0ZU2gb5OnYBTSJQMRfv91irmCz4KK B5Va5J7T7IGjHnR22PeAd+m3F3KW/voz4BMFPGNgxHE0Loq65M6+Pw==  The md5sum is d392b0f5516033f2ae0985745f299efd. ## Explanation The numbers in the file are increments of relative atomic mass (so the relative atomic mass of lithium is 1.008 + 0 + 2.995 + 2.937). This script works by converting the chemical formula into an arithmetical expression with + and *, replacing each symbol with its relative atomic mass, then feeding the expression to bc. If the formula contains invalid symbols, bc will give a syntax error and output nothing to STDOUT; in that case, the output is Invalid formula. If STDIN is empty, the output is 0. Nested brackets are supported. zcat y>g 2>a # Unzip the file y and output result to the file g. Send standard error to file a (there will be an error because the last 8 bytes are missing) s=(sed 's/[A-Z][a-z]*/& /g;q'<g) # Read from g to sed; append a space after any capital letter optionally followed by a lowercase letter; exit at the end of the first line so the atomic masses are not processed in this way; parse result as space-separated array and save to variable$s
p=1.008 # In the upcoming loop, $p is the value obtained in the previous iteration v=sed 's/[0-9]\+/*&/g;s/(/+(0/g' # Read from standard input to sed; prepend a * after any sequence of digits; replace ( with +(0; save to$v
r=(tail -1 g) # Get the last line of file g; parse as space-separated array; save to $r for n in {0..91};{ # Loop once for each number from 0 to 91; set$n to the current number each iteration
p=bc<<<$p+${r[n]} # Concatenate $p, + and the next number from$r; send to bc and evaluate as arithmetic expression; save to $p (i.e. add the next increment from the file to$p)
v=${v//${s[n]}/+$p} # Replace every occurence of the current element symbol with + and$p (i.e. replace each symbol with a + and its relative atomic mass
} # end loop
o=bc<<<0$v 2>a # Prepend 0 to$v; send to bc and evaluate as arithmetic expression; redirect any errors on standard error to the file a; save result to $o echo${o:-Invalid formula} # If $o is empty (if there was a syntax error), output Invalid formula; otherwise output$o


## Example

C6H2(NO2)3CH3  #input
C*6H*2+(0NO*2)*3CH*3  #after line 3
+12.011*6+*1.008*2+(0+14.007+15.999*2)*3+12.011+1.008*3  #after the loop in lines 4-6
0+12.011*6+*1.008*2+(0+14.007+15.999*2)*3+12.011+1.008*3  #given to bc in line 7
227.132 #output as evaluated by bc

• You could save a lot by storing the differences between the masses of the elements and calculating on the fly. The minimum and maximmm differences are -1.002 and +6.993 (either side of Pa.) By rearanging the elements in atomic mass order instead of atomic number order, the range becomes 0 to 5.991. When the periodic table was devised, there was much debate about which order was better. (Obviously, from a Chemistry point of view, the atomic number order is better, but they took some time deciding that.) – Level River St Aug 2 '14 at 14:11
• @steveverrill I thought about that when writing the code, I'll look into it soon – user16402 Aug 2 '14 at 14:38
• @steveverrill Done, the saving was about 33 chars – user16402 Aug 2 '14 at 16:09

# Perl - 924

It uses a series of regex substitution operations on the inputted formula to expand the subscripted elements and groups, replace the elements with atomic weights, and transform it into a sequence of additions which it then evaluates.

$_=<>;chop;@g='H1008He4003Li6940Be9012B10810C12011N14007O15999F18998Ne20179Na22989Mg24305Al26981Si28085P30974S32060Cl35450Ar39948K39098Ca40078Sc44955Ti47867V50942Cr51996Mn54938Fe55845Co58933Ni58693Cu63546Zn65380Ga69723Ge72630As74922Se78960Br79904Kr83798Rb85468Sr87620Y88906Zr91224Nb92906Mo95960Tc98Ru101070Rh102906Pd106420Ag107868Cd112411In114818Sn118710Sb121760Te127600I126904Xe131293Cs132905Ba137327La138905Ce140116Pr140907Nd144242Pm145Sm150360Eu151964Gd157250Tb158925Dy162500Ho164930Er167259Tm168934Yb173054Lu174967Hf178490Ta180948W183840Re186207Os190230Ir192217Pt195084Au196967Hg200592Tl204380Pb207200Bi208980Po209At210Rn222Fr223Ra226Ac227Th232038Pa231036U238028'=~/(\D+)([\d\.]+)/g;for$b(0..91){$d=2*$b+1;$g[$d]=$g[$d]>999?$g[$d]/1000:$g[$d]}%h=@g;for$a('($$((?>[^()]|(?1))*)$$)(\d+)','()([A-Z][a-z]?)(\d+)'){for(;s/$a/$2x$3/e;){}}s/([A-Z][a-z]?)/($h{$1}||_).'+'/ge;if(/_/){print'Invalid formula';exit}$_.=0;print eval;  # Mathematica 9 - 247 227 This is clearly cheating as I'm using a function that directly calculates atomatic masses (but not molar masses!): r=StringReplace;f[s_]:=Check[ToExpression@r[r[r[s,x:RegularExpression["[A-Z][a-z]*"]:>"ElementData[\""<>x<>"\",\"AtomicWeight\"]+"],x:DigitCharacter..:>"*"<>x<>"+"],{"+*"->"*","+"~~EndOfString->"","+)"->")"}],"Invalid formula"]  Usage: Call the function f with a string containing the formula, the output will be the mass. Test: f["H2O"] (* => 18.0153 *) f["O"] (* => 15.9994 *) f["C6H2(NO2)3CH3"] (* => 227.131 *) f["FOOF"] (* => 69.9956 *) f["C6H12Op3"] (* => Invalid formula *) f["Np"] (* => 237 *) f["((C)3)4"] (* => 144.128 *) f["CodeGolf"] (* => Invalid formula *)  Mathematica 10 doesn't output a raw number, but a number with a unit, so that may not be acceptable. • @MartinBüttner Thanks, I basically just copied a function from my init file and golfed it a little bit. The golfing could certainly be improved. – Tyilo Aug 2 '14 at 15:59 # Javascript, 1002 Input is in q and output is in a. I was unsure of what the rules for rounding were, so I truncated to 3 places after the decimal (or fewer, if digits were unavailable from Wikipedia). H=1.008 He=4.002 Li=6.94 Be=9.012 B=10.812 C=12.011 N=14.007 O=15.999 F=18.998 Ne=20.179 Na=22.989 Mg=24.305 Al=26.981 Si=28.085 P=30.973 S=32.06 Cl=35.45 Ar=39.948 K=39.098 Ca=40.078 Sc=44.955 Ti=47.867 V=50.941 Cr=51.996 Mn=54.938 Fe=55.845 Co=58.933 Ni=58.693 Cu=63.546 Zn=65.38 Ga=69.723 Ge=72.630 As=74.921 Se=78.96 Br=79.904 Kr=83.798 Rb=85.467 Sr=87.62 Y=88.905 Zr=91.224 Nb=92.906 Mo=95.96 Tc=98 Ru=101.07 Rh=102.905 Pd=106.42 Ag=107.868 Cd=112.411 In=114.818 Sn=118.710 Sb=121.760 Te=127.60 I=126.904 Xe=131.293 Cs=132.905 Ba=137.327 La=138.905 Ce=140.116 Pr=140.907 Nd=144.242 Pm=145 Sm=150.36 Eu=151.964 Gd=157.25 Tb=158.925 Dy=162.500 Ho=164.930 Er=167.259 Tm=168.934 Yb=173.054 Lu=174.966 Hf=178.49 Ta=180.947 W=183.84 Re=186.207 Os=190.23 Ir=192.217 Pt=195.084 Au=196.966 Hg=200.592 Tl=204.38 Pb=207.2 Bi=208.980 Po=209 At=210 Rn=222 Fr=223 Ra=226 Ac=227 Th=232.038 Pa=231.035 U=238.028 try{a=eval(q.replace(/(\d+)/g,'*$1').replace(/(\w)(?=[A-Z\(])/g,'$1+'))}catch(e){a="Invalid formula"}  • +1 for the idea of using simple variables ... now I can leave my overcomplex answer for future shame – edc65 Aug 2 '14 at 14:38 • Input is actually in q, though everything else seems fine. +1 – es1024 Aug 2 '14 at 21:20 • You could save some bytes by deleting the trailing zeros: 121.760 = 121.76 – Fels Aug 20 '14 at 8:32 # Javascript (E6) 1231 As a function with the input as argument and returning the output. Precision: 3 decimal digits Use regexp to transform the chemical formula in a simple arithmethic expression, with sums and products, replacing: • ( with +( • any numeric sequence with '*', then the numeric sequence • any Capital letter followed by letters with '+', then the atomic mass of the corresponding element (if found) Then the expression is evaluated and the value returned. In case of errors or if value is NaN (or zero) then function returns 'Invalid formula' Now I see that all other answers use the same method ... oh well here is the javascript version F=f=>{ T={H:1.008,He:4.002,Li:6.94,Be:9.012,B:10.81,C:12.011 ,N:14.007,O:15.999,F:18.998,Ne:20.179,Na:22.989,Mg:24.305 ,Al:26.981,Si:28.085,P:30.973,S:32.06,Cl:35.45,Ar:39.948 ,K:39.098,Ca:40.078,Sc:44.955,Ti:47.867,V:50.941,Cr:51.996,Mn:54.938 ,Fe:55.845,Co:58.933,Ni:58.693,Cu:63.546,Zn:65.38,Ga:69.723,Ge:72.630 ,As:74.921,Se:78.96,Br:79.904,Kr:83.798,Rb:85.467,Sr:87.62,Y:88.905,Zr:91.224 ,Nb:92.906,Mo:95.96,Tc:98,Ru:101.07,Rh:102.905,Pd:106.42,Ag:107.868,Cd:112.411 ,In:114.818,Sn:118.710,Sb:121.760,Te:127.60,I:126.904,Xe:131.293 ,Cs:132.905,Ba:137.327,La:138.905,Ce:140.116,Pr:140.907,Nd:144.242,Pm:145 ,Sm:150.36,Eu:151.964,Gd:157.25,Tb:158.925,Dy:162.500,Ho:164.930,Er:167.259 ,Tm:168.934,Yb:173.054,Lu:174.966,Hf:178.49,Ta:180.947,W:183.84,Re:186.207 ,Os:190.23,Ir:192.217,Pt:195.084,Au:196.966,Hg:200.592,Tl:204.38,Pb:207.2 ,Bi:208.980,Po:209,At:210,Rn:222,Fr:223,Ra:226,Ac:227,Th:232.038,Pa:231.035 ,U:238.028,Np:237,Pu:244,Am:243,Cm:247,Bk:247,Cf:251,Es:252,Fm:257,Md:258 ,No:259,Lr:266,Rf:267,Db:268,Sg:269,Bh:270,Hs:269,Mt:278 ,Ds:281,Rg:281,Cn:285,Uut:286,Fl:289,Uup:289,Lv:293,Uus:294,Uuo:294}; e='Invalid formula'; try{return eval(f.replace(/([A-Z][a-z]*)|(\d+)|(\()/g,(f,a,b,c)=>c?'+(':b?'*'+b:a='+'+T[a]))||e} catch(x){}return e }  • I used almost the same method, but I created the function at least a year ago, so at least I didn't copy you. ;) – Tyilo Aug 2 '14 at 16:10 ## PHP — 793 (583 + 210) Largely outdistanced by professorfish's answer, who is using a similar method, but hey… The symbols and the masses are gzip-compressed in the file a obtained with the following code: $symbolsList = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mg', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U'];
$massesList = [1.008, 4.003, 6.94, 9.012, 10.81, 12.011, 14.007, 15.999, 18.998, 20.18, 22.99, 24.305, 26.982, 28.085, 30.974, 32.06, 35.45, 39.948, 39.098, 40.078, 44.956, 47.867, 50.942, 51.996, 54.938, 55.845, 58.933, 58.6934, 63.546, 65.38, 69.726, 72.630, 74.922, 78.96, 79.904, 83.798, 85.468, 87.62, 88.906, 91.224, 92.906, 95.96, 98, 101.07, 102.906, 106.42, 107.868, 112.411, 114.818, 118.710, 121.760, 127.60, 126.904, 131.293, 132.905, 137.327, 138.905, 140.116, 140.908, 144.242, 145, 150.36, 151.964, 157.25, 158.925, 162.5, 164.93, 167.259, 168.934, 173.054, 174.967, 178.49, 180.948, 183.84, 186.207, 190.23, 192.217, 195.084, 196.967, 200.592, 204.38, 207.2, 208.98, 209, 210, 222, 223, 226, 227, 232.038, 231.036, 238.029];$fileArrayContent = [$symbolsList,$massesList];
$fileStringContent = json_encode($fileArrayContent);

$file = gzopen('a', 'w9'); gzwrite($file, $fileStringContent); gzclose($file);


The formula should be stored in the $f variable: $a=json_decode(gzfile('a')[0]);$r=@eval('return '.str_replace($a[0],$a[1],preg_replace(['#(?<=(?!\().)(\(|'.implode('|',$a[0]).')#','#\d+#'],['+${1}','*$0'],$f)).';');echo error_get_last()?'Invalid formula':$r;


Here is the ungolfed and commented version:

// Recover the data stored in the compressed file
$fileStringContent = gzfile('a')[0];$fileArrayContent = json_decode($fileStringContent);$symbolsList = $fileArrayContent[0];$massesList = $fileArrayContent[1];$formula = preg_replace('#(?<=(?!\().)(\(|'. implode('|', $symbolsList) .')#', '+${1}', $formula); // Add a "+" before each opening paranthesis and symbol not at the beginning of the string and not preceded by an opening paranthesis$formula = preg_replace('#\d+#', '*$0',$formula); // Add a "*" before each number

$formula = str_replace($symbolsList, $massesList,$formula); // Replace each symbol with its corresponding mass

$result = @eval('return '.$formula .';'); // Evaluate the value without showing the errors
echo error_get_last() ? 'Invalid formula' : $result; // Print the value, or "Invalid formula" if there was an error  • I'm beating you by one byte now, I have 673 bytes – user16402 Aug 2 '14 at 16:19 • @professorfish Indeed! Besides, I've miscounted the file size, which is larger that what I've said before. You're by far the winner, currently ^^! – Blackhole Aug 2 '14 at 16:50 # Scala, 1077 I see all your solutions in dynamically typed languages with cop-outs like eval or built-in atomic mass function and raise you a statically-typed language solution: object M{type S=String;type Z=(Int,S) val d="H *dHe JVLi inBe!!rB !5\"C !AiN !W!O !l3F \".*Ne\":_Na\"XUMg\"fUAl#%#Si#0iP #OOS #[&Cl$!,Ar$P|K$GxCa$RBSc%(7Ti%G5V %gwCr%s.Mn&4JFe&>)Co&^yNi&\\ECu'2\"Zn'ERGa'seGe(4^As(M#Se(x Br)$$Kr)MLRb)_5Sr)v,Y *%kZr*>LNb*PBMo*ppTc+(TRu+I4Rh+\\ePd,$,Ag,3RCd,cqIn,}LSn-HrSb-i>Te.IJI .B$Xe.peCs/#sBa/RwLa/ccCe/pXPr/y!Nd0>NPm0FTSm1!VEu12\\Gd1jrTb1|aDy2DdHo2^VEr2wATm3+0Yb3W Lu3k@Hf42nTa4L{W 4kfRe5&wOs5QdIr5fqPt6'BAu6;DHg6azTl7,8Pb7J8Bi7]2Po7]FAt7h$Rn9+bFr96@Ra9V8Ac9tTh:8NPa:-mU :x4".grouped(5).map{s=>(s.take(2).trim,s(2)*8836+s(3)*94+s(4)-285792)}.toMap
def k(n:S):Z={val x=n.takeWhile(_.isDigit);(if(x=="")1 else x.toInt,n drop x.length)}
def o(m:S):Z={if(m(0)==40){val(i,s)=a(m.tail);if(s(0)!=41)???;val(j,t)=k(s.tail);(j*i,t)}else{val l=if(m.size>1&&m(1).isLower)2 else 1;val(i,s)=d(m.take(l))->m.drop(l);val(j,t)=k(s);(j*i,t)}}
def a(m:S)={var(r,s)=(m,0);do{val(y,z)=o(r);r=z;s+=y}while(r!=""&&r(0)!=41);s->r}
def main(q:Array[S]){println(try{val(m,s)=a(io.Source.stdin.getLines.next);if(s!="")???;m/1e3}catch{case _=>"Invalid formula"})}}


I should consider some compression for the data, but for now let's just have the atomic masses in base 94, uncompressed.

• Gives me molarmass.scala:5: error: ';' expected but identifier found. def a(m:S)={var(r,s)=(m,0);do{val(y,z)=o(r);r=z;s+=y}while(r!=""&&r(0)!=41)s->r}` – es1024 Aug 2 '14 at 22:20
• Fixed. Also I found out Scala's parser is weird. – Karol S Aug 3 '14 at 11:53