Chemical calculator
Given a molecule's chemical formula, calculate the molar mass.
- Introduction
- A molecule consists of atoms. E.g. water, H2O, has two hydrogen atoms and one oxygen atom
- The mass of H2O is 1.008 * 2 + 15.999 = 18.015
- An atom name consists of one upper-case letter followed by zero, one or two lower-case letters.
- H (Hydrogen)
- He (Helium)
- Uue (Ununennium)
- The number of atoms is stated behind the atom or atom group
- An atom group is specified using parenthesis. E.g. Butyric acid, (CH3)2CHCOOH, has two CH3 groups
- A group may contain other groups, e.g. COOH(C(CH3)2)3CH3
- Background
- The mass is dimensionless. It is relative to 1/12 of Carbon-12
- Carbon-12 has exactly 6 protons, 6 electrons and 6 neutrons
- One mole of H2O has the mass 18.015 grams
- One mole is 6.02214076E23
- Atom masses
- A mapping between recognized element names and atomic mass follows in comma separated value format:
H,1.008 He,4.002602 Li,6.94 Be,9.0121831 B,10.81 C,12.011 N,14.007 O,15.999 F,18.998403163 Ne,20.1797 Na,22.98976928 Mg,24.305 Al,26.9815385 Si,28.085 P,30.973761998 S,32.06 Cl,35.45 K,39.0983 Ar,39.948 Ca,40.078 Sc,44.955908 Ti,47.867 V,50.9415 Cr,51.9961 Mn,54.938044 Fe,55.845 Ni,58.6934 Co,58.933194 Cu,63.546 Zn,65.38 Ga,69.723 Ge,72.63 As,74.921595 Se,78.971 Br,79.904 Kr,83.798 Rb,85.4678 Sr,87.62 Y,88.90584 Zr,91.224 Nb,92.90637 Mo,95.95 Ru,101.07 Rh,102.9055 Pd,106.42 Ag,107.8682 Cd,112.414 In,114.818 Sn,118.71 Sb,121.76 I,126.90447 Te,127.6 Xe,131.293 Cs,132.90545196 Ba,137.327 La,138.90547 Ce,140.116 Pr,140.90766 Nd,144.242 Pm,145 Sm,150.36 Eu,151.964 Gd,157.25 Tb,158.92535 Dy,162.5 Ho,164.93033 Er,167.259 Tm,168.93422 Yb,173.054 Lu,174.9668 Hf,178.49 Ta,180.94788 W,183.84 Re,186.207 Os,190.23 Ir,192.217 Pt,195.084 Au,196.966569 Hg,200.592 Tl,204.38 Pb,207.2 Bi,208.9804 Po,209 At,210 Rn,222 Fr,223 Ra,226 Ac,227 Pa,231.03588 Th,232.0377 Np,237 U,238.02891 Am,243 Pu,244 Cm,247 Bk,247 Cf,251 Es,252 Fm,257 Ubn,299 Uue,315
- Examples
<lang python>assert 1.008 == molar_mass('H') assert 2.016 == molar_mass('H2') assert 18.015 == molar_mass('H2O') assert 142.03553856000002 == molar_mass('Na2SO4') assert 84.162 == molar_mass('C6H12') assert 186.29499999999996 == molar_mass('COOH(C(CH3)2)3CH3')</lang>
- Link
CoffeeScript
No Regular Expression
ATOMIC_MASS = 'H':1.008 'C':12.011 'O':15.999 'Na':22.98976928 'S':32.06 'Uue':315
<lang coffeescript>molar_mass = (s) -> result = i = 0 member = (a,c) -> a <= s[i] <= c next = -> i += 1 s[i-1] while i < s.length if s[i] == '(' then result += '+' + next() else if s[i] == ')' then result += next() else if member '0','9' result += '*' result += next() while member '0','9' else if member 'A','Z' name = next() name += next() while member 'a','z' result += '+' + ATOMIC_MASS[name] parseFloat eval(result).toFixed 3
assert 1.008, molar_mass 'H' assert 2.016, molar_mass 'H2' assert 18.015, molar_mass 'H2O' assert 34.014, molar_mass 'H2O2' assert 34.014, molar_mass '(HO)2' assert 142.036, molar_mass 'Na2SO4' assert 84.162, molar_mass 'C6H12' assert 186.295, molar_mass 'COOH(C(CH3)2)3CH3' assert 176.124, molar_mass 'C6H4O2(OH)4' # Vitamin C assert 386.664, molar_mass 'C27H46O' # Cholesterol assert 315, molar_mass 'Uue'</lang>
Regular Expression
<lang coffeescript>mul = (match, p1, offset, string) -> '*' + p1 add = (match, p1, offset, string) -> if p1 == '(' then return '+' + p1 "+#{ATOMIC_MASS[p1]}"
molar_mass = (s) -> s = s.replace /(\d+)/g, mul s = s.replace /([A-Z][a-z]{0,2}|\()/g, add parseFloat(eval(s).toFixed(3))
assert 1.008, molar_mass('H') assert 2.016, molar_mass('H2') assert 18.015, molar_mass('H2O') assert 34.014, molar_mass('H2O2') assert 34.014, molar_mass('(HO)2') assert 142.036, molar_mass('Na2SO4') assert 84.162, molar_mass('C6H12') assert 186.295, molar_mass('COOH(C(CH3)2)3CH3') assert 176.124, molar_mass('C6H4O2(OH)4') # Vitamin C assert 386.664, molar_mass('C27H46O') # Cholesterol assert 315, molar_mass('Uue')</lang>
Factor
<lang factor>USING: assocs compiler.units definitions grouping infix.parser infix.private kernel math.functions math.parser multiline peg.ebnf qw sequences splitting strings words words.constant ; IN: rosetta-code.chemical-calculator
<< ! Do the stuff inside << ... >> at parse time.
HEREDOC: END H 1.008 He 4.002602 Li 6.94 Be 9.0121831 B 10.81 C 12.011 N 14.007 O 15.999 F 18.998403163 Ne 20.1797 Na 22.98976928 Mg 24.305 Al 26.9815385 Si 28.085 P 30.973761998 S 32.06 Cl 35.45 Ar 39.948 K 39.0983 Ca 40.078 Sc 44.955908 Ti 47.867 V 50.9415 Cr 51.9961 Mn 54.938044 Fe 55.845 Co 58.933194 Ni 58.6934 Cu 63.546 Zn 65.38 Ga 69.723 Ge 72.630 As 74.921595 Se 78.971 Br 79.904 Kr 83.798 Rb 85.4678 Sr 87.62 Y 88.90584 Zr 91.224 Nb 92.90637 Mo 95.95 Ru 101.07 Rh 102.90550 Pd 106.42 Ag 107.8682 Cd 112.414 In 114.818 Sn 118.710 Sb 121.760 Te 127.60 I 126.90447 Xe 131.293 Cs 132.90545196 Ba 137.327 La 138.90547 Ce 140.116 Pr 140.90766 Nd 144.242 Pm 145 Sm 150.36 Eu 151.964 Gd 157.25 Tb 158.92535 Dy 162.500 Ho 164.93033 Er 167.259 Tm 168.93422 Yb 173.054 Lu 174.9668 Hf 178.49 Ta 180.94788 W 183.84 Re 186.207 Os 190.23 Ir 192.217 Pt 195.084 Au 196.966569 Hg 200.592 Tl 204.38 Pb 207.2 Bi 208.98040 Po 209 At 210 Rn 222 Fr 223 Ra 226 Ac 227 Th 232.0377 Pa 231.03588 U 238.02891 Np 237 Pu 244 Am 243 Cm 247 Bk 247 Cf 251 Es 252 Fm 257 END
! Make constants from the pairs in the above string. " \n" split harvest 2 <groups> [
first2 [ [ "rosetta-code.chemical-calculator" create-word ] dip string>number define-constant ] 2curry with-compilation-unit
] each
>>
! Evaluate a string like "+C+O+O+H+(+C+(+C+H*3)*2)*3+C+H*3" ! Note that the infix vocabulary can work with the constants ! defined above.
- eval-infix ( seq -- n )
build-infix-ast infix-codegen prepare-operand call( -- x ) ;
! A grammar to put a + before every element/left paren and a * ! before every number and then evaluate the expression. EBNF: molar-mass [=[
number = [0-9]+ => "" like "*" prepend elt = [A-Z] [a-z]? [a-z]? => sift "" like "+" prepend lparen = "(" => "" like "+" prepend any = . => 1string mass = (elt|number|lparen|any)+ => concat eval-infix
]=]
! assert= doesn't work due to floating point weirdness. ERROR: failed-assertion expected +/- got ;
- approx-assert= ( x y epsilon -- )
3dup ~ [ 3drop ] [ swap failed-assertion ] if ;
- chemical-calculator-demo ( -- )
{ { 1.008 "H" } { 2.016 "H2" } { 18.015 "H2O" } { 142.03553856 "Na2SO4" } { 84.16200000000001 "C6H12" } { 186.295 "COOH(C(CH3)2)3CH3" } } [ molar-mass 1e-5 approx-assert= ] assoc-each ;
MAIN: chemical-calculator-demo</lang>
No assertion errors.
Go
This doesn't use regular expressions, RPN or eval (which Go doesn't have). It's just a simple molar mass evaluator written from scratch. <lang go>package main
import (
"fmt" "strconv" "strings"
)
var atomicMass = map[string]float64{
"H": 1.008, "He": 4.002602, "Li": 6.94, "Be": 9.0121831, "B": 10.81, "C": 12.011, "N": 14.007, "O": 15.999, "F": 18.998403163, "Ne": 20.1797, "Na": 22.98976928, "Mg": 24.305, "Al": 26.9815385, "Si": 28.085, "P": 30.973761998, "S": 32.06, "Cl": 35.45, "Ar": 39.948, "K": 39.0983, "Ca": 40.078, "Sc": 44.955908, "Ti": 47.867, "V": 50.9415, "Cr": 51.9961, "Mn": 54.938044, "Fe": 55.845, "Co": 58.933194, "Ni": 58.6934, "Cu": 63.546, "Zn": 65.38, "Ga": 69.723, "Ge": 72.630, "As": 74.921595, "Se": 78.971, "Br": 79.904, "Kr": 83.798, "Rb": 85.4678, "Sr": 87.62, "Y": 88.90584, "Zr": 91.224, "Nb": 92.90637, "Mo": 95.95, "Ru": 101.07, "Rh": 102.90550, "Pd": 106.42, "Ag": 107.8682, "Cd": 112.414, "In": 114.818, "Sn": 118.710, "Sb": 121.760, "Te": 127.60, "I": 126.90447, "Xe": 131.293, "Cs": 132.90545196, "Ba": 137.327, "La": 138.90547, "Ce": 140.116, "Pr": 140.90766, "Nd": 144.242, "Pm": 145, "Sm": 150.36, "Eu": 151.964, "Gd": 157.25, "Tb": 158.92535, "Dy": 162.500, "Ho": 164.93033, "Er": 167.259, "Tm": 168.93422, "Yb": 173.054, "Lu": 174.9668, "Hf": 178.49, "Ta": 180.94788, "W": 183.84, "Re": 186.207, "Os": 190.23, "Ir": 192.217, "Pt": 195.084, "Au": 196.966569, "Hg": 200.592, "Tl": 204.38, "Pb": 207.2, "Bi": 208.98040, "Po": 209, "At": 210, "Rn": 222, "Fr": 223, "Ra": 226, "Ac": 227, "Th": 232.0377, "Pa": 231.03588, "U": 238.02891, "Np": 237, "Pu": 244, "Am": 243, "Cm": 247, "Bk": 247, "Cf": 251, "Es": 252, "Fm": 257, "Uue": 315, "Ubn": 299, "@a": 0, // for evaluating parenthesized expressions "@b": 0, // ditto "@c": 0, // ditto
}
func replaceParens(s string) string {
var exprs []string var letter byte = 'a' for { start := strings.IndexByte(s, '(') if start == -1 { break } restart: for i := start + 1; i < len(s); i++ { if s[i] == ')' { expr := s[start+1 : i] exprs = append(exprs, expr) symbol := fmt.Sprintf("@%c", letter) s = strings.Replace(s, s[start:i+1], symbol, 1) atomicMass[symbol] = evaluate(expr) letter++ break } if s[i] == '(' { start = i goto restart } } } return s
}
func evaluate(s string) float64 {
s += string('[') // add end of string marker var symbol, number string sum := 0.0 for i := 0; i < len(s); i++ { c := s[i] switch { case c >= '@' && c <= '[': // @, A-Z, [ n := 1 if number != "" { n, _ = strconv.Atoi(number) } if symbol != "" { sum += atomicMass[symbol] * float64(n) } if c == '[' { break } symbol = string(c) number = "" case c >= 'a' && c <= 'z': symbol += string(c) case c >= '0' && c <= '9': number += string(c) default: panic(fmt.Sprintf("Unexpected symbol %c in molecule", c)) } } return sum
}
func main() {
molecules := []string{ "H", "H2", "H2O", "H2O2", "(HO)2", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3", "C6H4O2(OH)4", "C27H46O", "Uue", } for _, molecule := range molecules { mass := evaluate(replaceParens(molecule)) fmt.Printf("%17s -> %7.3f\n", molecule, mass) }
}</lang>
- Output:
H -> 1.008 H2 -> 2.016 H2O -> 18.015 H2O2 -> 34.014 (HO)2 -> 34.014 Na2SO4 -> 142.036 C6H12 -> 84.162 COOH(C(CH3)2)3CH3 -> 186.295 C6H4O2(OH)4 -> 176.124 C27H46O -> 386.664 Uue -> 315.000
Julia
Note that Julia's 64-bit floating point gets a slightly different result for one of the assertions, hence a small change in the last example. The function uses Julia's own language parser to evaluate the compound as an arithmetic expression. <lang julia>const H = 1.008 const He = 4.002602 const Li = 6.94 const Be = 9.0121831 const B = 10.81 const C = 12.011 const N = 14.007 const O = 15.999 const F = 18.998403163 const Ne = 20.1797 const Na = 22.98976928 const Mg = 24.305 const Al = 26.9815385 const Si = 28.085 const P = 30.973761998 const S = 32.06 const Cl = 35.45 const Ar = 39.948 const K = 39.0983 const Ca = 40.078 const Sc = 44.955908 const Ti = 47.867 const V = 50.9415 const Cr = 51.9961 const Mn = 54.938044 const Fe = 55.845 const Co = 58.933194 const Ni = 58.6934 const Cu = 63.546 const Zn = 65.38 const Ga = 69.723 const Ge = 72.630 const As = 74.921595 const Se = 78.971 const Br = 79.904 const Kr = 83.798 const Rb = 85.4678 const Sr = 87.62 const Y = 88.90584 const Zr = 91.224 const Nb = 92.90637 const Mo = 95.95 const Ru = 101.07 const Rh = 102.90550 const Pd = 106.42 const Ag = 107.8682 const Cd = 112.414 const In = 114.818 const Sn = 118.710 const Sb = 121.760 const Te = 127.60 const I = 126.90447 const Xe = 131.293 const Cs = 132.90545196 const Ba = 137.327 const La = 138.90547 const Ce = 140.116 const Pr = 140.90766 const Nd = 144.242 const Pm = 145 const Sm = 150.36 const Eu = 151.964 const Gd = 157.25 const Tb = 158.92535 const Dy = 162.500 const Ho = 164.93033 const Er = 167.259 const Tm = 168.93422 const Yb = 173.054 const Lu = 174.9668 const Hf = 178.49 const Ta = 180.94788 const W = 183.84 const Re = 186.207 const Os = 190.23 const Ir = 192.217 const Pt = 195.084 const Au = 196.966569 const Hg = 200.592 const Tl = 204.38 const Pb = 207.2 const Bi = 208.98040 const Po = 209 const At = 210 const Rn = 222 const Fr = 223 const Ra = 226 const Ac = 227 const Th = 232.0377 const Pa = 231.03588 const U = 238.02891 const Np = 237 const Pu = 244 const Am = 243 const Cm = 247 const Bk = 247 const Cf = 251 const Es = 252 const Fm = 257
function molar_mass(s)
s = replace(s, r"\d+" => (x) -> "*" * x) s = replace(s, r"[A-Z][a-z]{0,2}|\(" => (x) ->"+" * x) eval(Meta.parse(s))
end
@assert 1.008 == molar_mass("H") @assert 2.016 == molar_mass("H2") @assert 18.015 == molar_mass("H2O") @assert 142.03553856000002 == molar_mass("Na2SO4") @assert 84.162 == molar_mass("C6H12") @assert 186.29500000000002 == molar_mass("COOH(C(CH3)2)3CH3") </lang> No assertion errors.
Nim
<lang python>#? replace(sub = "\t", by = " ")
- Nim lacks runtime eval, that's the reason for so much code.
- Also, seqs can't contain mixed types.
from atomicMass import ATOMIC_MASS import tables, strutils, sequtils, math
proc pass1(s:string): seq[string] = # "H2O" => @["H","*","2","+","O"] result.add "0" var i = 0 proc member(a:char,c:char): bool = i < s.len and a <= s[i] and s[i] <= c proc next(): char = i += 1 s[i-1] while i < s.len: if s[i] == '(': result = result.concat @["+","("] discard next() elif s[i] == ')': result.add $next() elif member('0','9'): var number = "" result.add "*" while member('0','9'): number &= $next() result.add number elif member('A','Z'): if i>0 and s[i-1] != '(': result.add "+" var name = "" name.add next() while member('a','z'): name.add next() result.add name
proc pass2(s:string): seq[string] = # "H2O" => @["H", "2", "*", "O", "+"] let ops = "+*" var tokens = pass1 s var stack: seq[string] var op: string
for token in tokens: case token of "(": stack.add token of ")": while stack.len > 0: op = stack.pop() if op == "(": break result.add op else: if token in ops: while stack.len > 0: op = stack[^1] if not (op in ops): break if ops.find(token) >= ops.find(op): break discard stack.pop() result.add op stack.add token else: result.add token
while stack.len > 0: result.add stack.pop()
proc pass3(s:string): Table[string,int] = # H 2 * O + => { H:2, O:1 } let rpn: seq[string] = pass2 s var stack: seq[Table[string,int]] = @[] for item in rpn: if item == "+": let h1:Table[string,int] = stack.pop() let h2:Table[string,int] = stack.pop() var res: Table[string,int] = initTable[string,int]() for key in h1.keys: if key != "factor": res[key] = h1[key] for key in h2.keys: if key != "factor": if h1.haskey key: res[key] = h1[key] + h2[key] else: res[key] = h2[key] stack.add res elif item == "*": let top: Table[string,int] = stack.pop() # let hash: Table[string,int] = stack.pop() let factor: int = top["factor"] var res: Table[string,int] = initTable[string,int]() for key in hash.keys: let str : string = key let value: int = hash[key] res[key] = value * factor stack.add res elif ATOMIC_MASS.haskey(item): let res : Table[string,int] = {item: 1}.toTable stack.add res else: # number let factor : int = parseInt item let res : Table[string,int] = {"factor": factor}.toTable stack.add res return stack.pop()
proc pass4(s: string) : float = # { H:2, O:1 } => 18.015 let atoms: Table[string,int] = pass3 s for key in atoms.keys: let count : int = atoms[key] result += float(count) * ATOMIC_MASS[key] round result,3
let molar_mass = pass4
assert 18.015 == molar_mass "H2O" assert 34.014 == molar_mass "H2O2" assert 34.014 == molar_mass "(HO)2" assert 142.036 == molar_mass "Na2SO4" assert 84.162 == molar_mass "C6H12" assert 186.295 == molar_mass "COOH(C(CH3)2)3CH3" assert 176.124 == molar_mass "C6H4O2(OH)4" # Vitamin C assert 386.664 == molar_mass "C27H46O" # Cholesterol assert 315 == molar_mass "Uue"</lang>
Perl
<lang perl>use strict; use warnings; use List::Util; use Parse::RecDescent;
my $g = Parse::RecDescent->new(<<'EOG');
{ my %atomic_weight = <H 1.008 C 12.011 O 15.999 Na 22.99 S 32.06> }
weight : compound { $item[1] } compound : group(s) { List::Util::sum( @{$item[1]} ) } group : element /\d+/ { $item[1] * $item[2] } | element { $item[1] } element : /[A-Z][a-z]*/ { $atomic_weight{ $item[1] } } | "(" compound ")" { $item[2] }
EOG
for (<H H2 H2O Na2SO4 C6H12 COOH(C(CH3)2)3CH3>) {
printf "%7.3f %s\n", $g->weight($_), $_;
}</lang>
- Output:
1.008 H 2.016 H2 18.015 H2O 142.036 Na2SO4 84.162 C6H12 186.295 COOH(C(CH3)2)3CH3
Perl 6
<lang perl6>my %ATOMIC_MASS =
H => 1.008 , Fe => 55.845 , Te => 127.60 , Ir => 192.217 , He => 4.002602 , Co => 58.933194 , I => 126.90447 , Pt => 195.084 , Li => 6.94 , Ni => 58.6934 , Xe => 131.293 , Au => 196.966569 , Be => 9.0121831 , Cu => 63.546 , Cs => 132.90545196 , Hg => 200.592 , B => 10.81 , Zn => 65.38 , Ba => 137.327 , Tl => 204.38 , C => 12.011 , Ga => 69.723 , La => 138.90547 , Pb => 207.2 , N => 14.007 , Ge => 72.630 , Ce => 140.116 , Bi => 208.98040 , O => 15.999 , As => 74.921595 , Pr => 140.90766 , Po => 209 , F => 18.998403163 , Se => 78.971 , Nd => 144.242 , At => 210 , Ne => 20.1797 , Br => 79.904 , Pm => 145 , Rn => 222 , Na => 22.98976928 , Kr => 83.798 , Sm => 150.36 , Fr => 223 , Mg => 24.305 , Rb => 85.4678 , Eu => 151.964 , Ra => 226 , Al => 26.9815385 , Sr => 87.62 , Gd => 157.25 , Ac => 227 , Si => 28.085 , Y => 88.90584 , Tb => 158.92535 , Th => 232.0377 , P => 30.973761998 , Zr => 91.224 , Dy => 162.500 , Pa => 231.03588 , S => 32.06 , Nb => 92.90637 , Ho => 164.93033 , U => 238.02891 , Cl => 35.45 , Mo => 95.95 , Er => 167.259 , Np => 237 , Ar => 39.948 , Ru => 101.07 , Tm => 168.93422 , Pu => 244 , K => 39.0983 , Rh => 102.90550 , Yb => 173.054 , Am => 243 , Ca => 40.078 , Pd => 106.42 , Lu => 174.9668 , Cm => 247 , Sc => 44.955908 , Ag => 107.8682 , Hf => 178.49 , Bk => 247 , Ti => 47.867 , Cd => 112.414 , Ta => 180.94788 , Cf => 251 , V => 50.9415 , In => 114.818 , W => 183.84 , Es => 252 , Cr => 51.9961 , Sn => 118.710 , Re => 186.207 , Fm => 257 , Mn => 54.938044 , Sb => 121.760 , Os => 190.23 ,
grammar Chemical_formula {
my @ATOMIC_SYMBOLS = %ATOMIC_MASS.keys.sort;
rule TOP { ^ (<lparen>|<rparen>|<element>)+ $ } token quantity { \d+ } token lparen { '(' } token rparen { ')' <quantity>? } token element { $<e>=[@ATOMIC_SYMBOLS] <quantity>? }
} class Chemical_formula_actions {
has @stack = 0; method TOP ($/) { $/.make: @stack } method lparen ($/) { push @stack, 0 } method rparen ($/) { my $m = @stack.pop; @stack[*-1] += ($<quantity> // 1) * $m } method element ($/) { @stack[*-1] += ($<quantity> // 1) * %ATOMIC_MASS{~$<e>} }
} sub molar_mass ( Str $formula --> Real ) {
Chemical_formula.parse( $formula, :actions(Chemical_formula_actions.new) ) orelse die "Chemical formula not recognized: '$formula'"; return $/.made.[0];
} say .&molar_mass.fmt('%7.3f '), $_ for <H H2 H2O Na2SO4 C6H12 COOH(C(CH3)2)3CH3>;</lang>
- Output:
1.008 H 2.016 H2 18.015 H2O 142.036 Na2SO4 84.162 C6H12 186.295 COOH(C(CH3)2)3CH3
Python
<lang python>import re
mul = lambda x : '*' + x.group(0) def add(x) : name = x.group(0) return '+' + name if name == '(' else '+' + str(ATOMIC_MASS[name])
def molar_mass(s): s = re.sub(r"\d+", mul, s) s = re.sub(r"[A-Z][a-z]{0,2}|\(", add, s) return round(eval(s),3)
assert 1.008 == molar_mass('H') assert 2.016 == molar_mass('H2') assert 18.015 == molar_mass('H2O') assert 34.014 == molar_mass('H2O2') assert 34.014 == molar_mass('(HO)2') assert 142.036 == molar_mass('Na2SO4') assert 84.162 == molar_mass('C6H12') assert 186.295 == molar_mass('COOH(C(CH3)2)3CH3') assert 176.124 == molar_mass('C6H4O2(OH)4') # Vitamin C assert 386.664 == molar_mass('C27H46O') # Cholesterol assert 315 == molar_mass('Uue')</lang>
zkl
Really bad error checking <lang zkl>fcn molarMass(str,mass=0.0){
while(span:=str.span("(",")",False)){ // get inner most () group group:=str[span.xplode()]; // (CH3) str =str.del(span.xplode()); // nuke (CH3) w :=molarMass(group[1,-1],mass); // remove "(" & ")" i,s2 := span[0], str[i,*]; if(m.search(s2)) // well crap, (CH3)2 { z:=m.matched[1]; str=str.del(i,z.len()); mass=w*z.toInt() } else mass=w; } ms:=List(mass); // HO --> (1.008,15.999).sum() while(str){ if(not atomRE.search(str)) throw(Exception.ValueError); ns,nm,n := atomRE.matched; n=(if(n) n.toInt() else 1); // H2 ms.append(atomicMass[nm]*n); str=str.del(ns.xplode()); // nuke H or H2 } ms.reduce('+);
}</lang> <lang zkl>var [const] atomicMass = Dictionary(
"Ac",227.000000, "Ag",107.868200, "Al", 26.981538, "Am",243.000000, "Ar", 39.948000, "As", 74.921595, "At",210.000000, "Au",196.966569, "B" , 10.810000, "Ba",137.327000, "Be", 9.012183, "Bi",208.980400, "Bk",247.000000, "Br", 79.904000, "C" , 12.011000, "Ca", 40.078000, "Cd",112.414000, "Ce",140.116000, "Cf",251.000000, "Cl", 35.450000, "Cm",247.000000, "Co", 58.933194, "Cr", 51.996100, "Cs",132.905452, "Cu", 63.546000, "Dy",162.500000, "Er",167.259000, "Es",252.000000, "Eu",151.964000, "F" , 18.998403, "Fe", 55.845000, "Fm",257.000000, "Fr",223.000000, "Ga", 69.723000, "Gd",157.250000, "Ge", 72.630000, "H" , 1.008000, "He", 4.002602, "Hf",178.490000, "Hg",200.592000, "Ho",164.930330, "I" ,126.904470, "In",114.818000, "Ir",192.217000, "K" , 39.098300, "Kr", 83.798000, "La",138.905470, "Li", 6.940000, "Lu",174.966800, "Mg", 24.305000, "Mn", 54.938044, "Mo", 95.950000, "N" , 14.007000, "Na", 22.989769, "Nb", 92.906370, "Nd",144.242000, "Ne", 20.179700, "Ni", 58.693400, "Np",237.000000, "O" , 15.999000, "Os",190.230000, "P" , 30.973762, "Pa",231.035880, "Pb",207.200000, "Pd",106.420000, "Pm",145.000000, "Po",209.000000, "Pr",140.907660, "Pt",195.084000, "Pu",244.000000, "Ra",226.000000, "Rb", 85.467800, "Re",186.207000, "Rh",102.905500, "Rn",222.000000, "Ru",101.070000, "S" , 32.060000, "Sb",121.760000, "Sc", 44.955908, "Se", 78.971000, "Si", 28.085000, "Sm",150.360000, "Sn",118.710000, "Sr", 87.620000, "Ta",180.947880, "Tb",158.925350, "Te",127.600000, "Th",232.037700, "Ti", 47.867000, "Tl",204.380000, "Tm",168.934220, "U" ,238.028910, "V" , 50.941500, "W" ,183.840000, "Xe",131.293000, "Y" , 88.905840, "Yb",173.054000, "Zn", 65.380000, "Zr", 91.224000,
), m=RegExp("([1-9]+)"),
atomRE=fcn{ // sort by name length, build RE: "(Lu|Es|Er..|W|Y)([1-9]*)"
nms:=atomicMass.keys; ( [(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1 'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten() .concat("|","(",")([1-9]*)") ) : RegExp(_);
}();</lang>
<lang zkl>foreach cstr in (T("H","H2","H2O","Na2SO4","C6H12","COOH(C(CH3)2)3CH3"))
{ println(cstr," --> ",molarMass(cstr)) }</lang>
- Output:
H --> 1.008 H2 --> 2.016 H2O --> 18.015 Na2SO4 --> 142.036 C6H12 --> 84.162 COOH(C(CH3)2)3CH3 --> 186.295