Chemical calculator: Difference between revisions

From Rosetta Code
Content added Content deleted
(Add CSV of atomic numbers)
Line 23: Line 23:
* One mole of H2O has the mass 18.015 grams
* One mole of H2O has the mass 18.015 grams
* One mole is 6.02214076E23
* One mole is 6.02214076E23
* A mapping between recognised element names and atomic mass follows in comma separated value format:
<div style="overflow:scroll; height:200px;">
<pre>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</pre>
</div>



;Examples:
;Examples:

Revision as of 11:41, 19 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.

This application calculates the molar mass given the molecule's chemical formula.

Introduction
  • A molecule consists of atoms. E.g. water, H2O has two hydrogen atoms and one oxygen atom
  • The mass of water, H2O is 1.008 * 2 + 15.999 = 18.015
  • An atom name consists of one upper-case letter followed by zero or one lower-case letter.
    • H (Hydrogen)
    • He (Helium)
  • In the future one more lower-case letter might be used.
    • Uue (Ununennium)
    • Ubn (Unbinilium)
  • 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
  • The atom masses are available in the Python section below
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
  • A mapping between recognised 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


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

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>

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,

}

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

   for i := 0; i < len(s); {
       switch {
       case s[i] >= 'A' && s[i] <= 'Z':
           if i+1 < len(s) && s[i+1] >= 'a' && s[i+1] <= 'z' {
               result = append(result, fmt.Sprintf("%c%c", s[i], s[i+1]))
               pattern += "A"
               i += 2
           } else {
               result = append(result, string(s[i]))
               pattern += "A"
               i++
           }
       case s[i] >= '0' && s[i] <= '9':
           antal := int(s[i]) - 48
           i++
           for i < len(s) && s[i] >= '0' && s[i] <= '9' {
               antal = antal*10 + int(s[i]) - 48
               i++
           }
           result = append(result, strconv.Itoa(antal))
           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"}
   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

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.

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

Without 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

<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){ 
     nm:=atoms.filter1('wrap(a){ a.glob(str,0) });  // case matters
     if(not nm) 	// H2, didn't match 2, look for 2 now

{ m.search(str); nm=m.matched[1]; ms[-1]*=nm.toInt(); }

     else ms.append(atomicMass[nm=nm[0,-1]]);  // "H*"-->"H"
     str=str.del(0,nm.len());		        // nuke H or 2
  }
  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]+)"),

  atoms=fcn{  // sort by name length, tack on "*" so "H*" globs H2
     nms:=atomicMass.keys;
     [(nms.apply("len") : (0).max(_)) .. 1, -1].pump(List, // 2..1
        'wrap(n){ nms.filter('wrap(nm){ nm.len()==n }) }).flatten()
     .apply('+("*"));
  }();		// --> (Lu*, Es*, Er*, Eu*, ...  V*, W*, Y*)</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