Chemical calculator: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|zkl}}: switch to regular expression)
No edit summary
Line 715: Line 715:
</lang>
</lang>
No assertion errors.
No assertion errors.

=={{header|Nim}}==
<lang python>#? replace(sub = "\t", by = " ")

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>


=={{header|Perl 6}}==
=={{header|Perl 6}}==

Revision as of 10:02, 20 March 2019

Chemical calculator is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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

Molecular mass

CoffeeScript

No Regular Expression

<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

Translation of: Julia

<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

Works with: Factor version 0.98

<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

Translation of: Python

<lang go>package main

import (

   "fmt"
   "strconv"
   "strings"

)

type any = interface{}

type ht = map[string]int

type Stack []any

func (s *Stack) push(a any) {

   *s = append(*s, a)

}

func (s *Stack) pop() any {

   le := len(*s)
   if le == 0 {
       panic("Attempt to pop from an empty stack")
   }
   le--
   a := (*s)[le]
   *s = (*s)[:le]
   return a

}

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,

}

// "H2O" => H 2 O and "A1A" func parse(s string) (result []string, pattern string) {

   for i := 0; i < len(s); {
       switch {
       case s[i] >= 'A' && s[i] <= 'Z':
           name := string(s[i])
           i++
           for i < len(s) && s[i] >= 'a' && s[i] <= 'z' {
               name += string(s[i])
               i++
           }
           result = append(result, name)
           pattern += "A"           
       case s[i] >= '0' && s[i] <= '9':
           count := string(s[i])
           i++
           for i < len(s) && s[i] >= '0' && s[i] <= '9' {
               count += string(s[i])
               i++
           }
           result = append(result, count)
           pattern += "1"
       default:
           result = append(result, string(s[i]))
           pattern += string(s[i])
           i++
       }
   }
   return

}

// H 2 O => H * 2 + O func pass1(m1 []string, m2 string) (result []string) {

   const symbols = "A1()"
   matrix := [4]string{
       "+*+=", // A = Atom
       "+x+=", // 1 = Count
       "=x=x", // (
       "+*+=", // )
   }
   add := func(a string) []string { return []string{a, "+"} }
   mul := func(a string) []string { return []string{a, "*"} }
   err := func(a string) []string { return []string{} }
   nop := func(a string) []string { return []string{a} }
   operation := map[byte]func(string) []string{
       '+': add, '*': mul, 'x': err, '=': nop,
   }
   for i := 0; i < len(m1)-1; i++ {
       ch0 := m2[i]
       ch1 := m2[i+1]
       i0 := strings.IndexByte(symbols, ch0)
       i1 := strings.IndexByte(symbols, ch1)
       op := matrix[i0][i1]
       result = append(result, operation[op](m1[i])...)
   }
   result = append(result, m1[len(m1)-1])
   return

}

// H * 2 + O => H 2 * O + func pass2(tokens []string) (result []string) {

   ops := "+*"
   stack := new(Stack)
   for _, token := range tokens {
       switch token {
       case "(":
           stack.push(token)
       case ")":
           for len(*stack) > 0 {
               op := stack.pop().(string)
               if op == "(" {
                   break
               }
               result = append(result, op)
           }
       default:
           if strings.Index(ops, token) >= 0 {
               for len(*stack) > 0 {
                   op := (*stack)[len(*stack)-1].(string)
                   if strings.Index(ops, op) == -1 {
                       break
                   }
                   if strings.Index(ops, token) >= strings.Index(ops, op) {
                       break
                   }
                   stack.pop()
                   result = append(result, op)
               }
               stack.push(token)
           } else {
               result = append(result, token)
           }
       }
   }
   for len(*stack) > 0 {
       result = append(result, stack.pop().(string))
   }
   return

}

// H 2 * O + => { H:2, O:1 } func pass3(rpn []string) ht {

   stack := new(Stack)
   for _, item := range rpn {
       switch {
       case item == "+":
           h1 := stack.pop().(ht)
           h2 := (*stack)[len(*stack)-1].(ht)
           for key := range h1 {
               if h2[key] > 0 {
                   h2[key] += h1[key]
               } else {
                   h2[key] = h1[key]
               }
           }
       case item == "*":
           antal, _ := strconv.Atoi(stack.pop().(string))
           hash := (*stack)[len(*stack)-1].(ht)
           for key := range hash {
               hash[key] *= antal
           }
       case atomicMass[item] > 0.0:
           res := make(ht)
           res[item] = 1
           stack.push(res)
       default:
           stack.push(item)
       }
   }
   return stack.pop().(ht)

}

// { H:2, O:1 } => 18.015 func pass4(atoms ht) float64 {

   sum := 0.0
   for key := range atoms {
       sum += atomicMass[key] * float64(atoms[key])
   }
   return sum

}

func molarMass(molecule string) float64 {

   atomList, pattern := parse(molecule)
   infix := pass1(atomList, pattern)
   rpn := pass2(infix)
   atoms := pass3(rpn)
   return pass4(atoms)

}

func main() {

   molecules := []string {
       "H", "H2", "H2O", "Na2SO4", "C6H12", "COOH(C(CH3)2)3CH3", 
       "C6H4O2(OH)4", "C27H46O", "Uue",
   }
   for _, molecule := range molecules {
       mass := molarMass(molecule)
       fmt.Printf("%17s -> %g\n", molecule, mass)
   }

}</lang>

Output:
                H -> 1.008
               H2 -> 2.016
              H2O -> 18.015
           Na2SO4 -> 142.03553856000002
            C6H12 -> 84.162
COOH(C(CH3)2)3CH3 -> 186.29499999999996
      C6H4O2(OH)4 -> 176.124
          C27H46O -> 386.664
              Uue -> 315

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 = " ")

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 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

No Regular Expression

<lang python>ATOMIC_MASS = { '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, }

def parse(s): # H2O => H 2 O and 'A1A' result = [] pattern = i = 0 while i < len(s): if s[i].isupper(): name = s[i] i += 1 while i < len(s) and s[i].islower(): name += s[i] i += 1 result.append(name) pattern += 'A' elif s[i].isdigit(): count = s[i] i += 1 while i < len(s) and s[i].isdigit(): count += s[i] i += 1 result.append(count) pattern += '1' else: result.append(s[i]) pattern += s[i] i+=1 return result,pattern

def pass1(m1,m2): # H 2 O => H * 2 + O SYMBOLS = 'A1()' matrix = [ '+*+=', # A = Atom '+x+=', # 1 = Count '=x=x', # ( '+*+='] # )

result = []

add = lambda a, b: [a, '+'] mul = lambda a, b: [a, '*'] error = lambda a, b: [] nop = lambda a, b: [a] operation = {'+': add, '*': mul, 'x': error, '=': nop}

for i in range(len(m1)-1): ch0 = m2[i] ch1 = m2[i+1] i0 = SYMBOLS.index(ch0) i1 = SYMBOLS.index(ch1) op = matrix[i0][i1] result += operation[op](m1[i],m1[i+1]) result.append(m1[-1]) return result

def pass2(tokens): # H * 2 + O => H 2 * O + ops = "+*" stack = [] result = []

for token in tokens: if token == '(': stack.append(token) elif token == ')': while len(stack) > 0: op = stack.pop() if op == '(': break result.append(op) else: if token in ops: while len(stack) > 0: op = stack[-1] if not (op in ops): break if ops.find(token) >= ops.find(op): break stack.pop() result.append(op) stack.append(token) else: result.append(token)

while len(stack) > 0: result.append(stack.pop()) return result

def pass3(rpn): # H 2 * O + => { H:2, O:1 } stack = [] for item in rpn: if item == '+': h1 = stack.pop() h2 = stack[-1] for key in h1: if key in h2: h2[key] += h1[key] else: h2[key] = h1[key] elif item == '*': antal = int(stack.pop()) hash = stack[-1] for key in hash: hash[key] *= antal elif item in ATOMIC_MASS: res = {} res[item] = 1 stack.append(res) else: stack.append(item) return stack.pop()

def pass4(atoms): # { H:2, O:1 } => 18.015 return sum([ATOMIC_MASS[key] * atoms[key] for key in atoms])

def molar_mass(molecule): atom_list,pattern = parse(molecule) infix = pass1(atom_list,pattern) rpn = pass2(infix) atoms = pass3(rpn) return pass4(atoms)

#assert 'Na 10 C O O H ( C ( C H 3 ) 2 ) 3 C H 3' == ' '.join(atom_list) #assert 'A1AAAA(A(AA1)1)1AA1' == pattern #assert 'Na * 10 + C + O + O + H + ( C + ( C + H * 3 ) * 2 ) * 3 + C + H * 3' == (' '.join(infix)) #assert 'Na 10 * C O O H C C H 3 * + 2 * + 3 * C H 3 * + + + + + + +' == ' '.join(rpn) #assert {'Na':10, 'C': 11, 'O': 2, 'H': 22} == atoms

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>

Regular Expression

Translation of: Julia

<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