Chemical calculator: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Perl 6}}: Added Perl 6 solution)
Line 453: Line 453:
</lang>
</lang>
No assertion errors.
No assertion errors.

=={{header|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>
{{Out}}
<pre> 1.008 H
2.016 H2
18.015 H2O
142.036 Na2SO4
84.162 C6H12
186.295 COOH(C(CH3)2)3CH3</pre>


=={{header|Python}}==
=={{header|Python}}==

Revision as of 06:21, 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 12 protons, 12 electrons and 12 neutrons
  • One mole of H2O has the mass 18.015 grams
  • One mole is 6.02214076E23
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

https://en.wikipedia.org/wiki/Molecular_mass

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

Atom masses

<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 }</lang>

Solution

<lang python>from atomic_mass import ATOMIC_MASS

def parse(s): # 'H2O' => H 2 O result = [] pattern = i = 0 while i<len(s): if s[i].isupper(): if i+1<len(s) and s[i+1].islower(): result.append(s[i]+s[i+1]) pattern += 'A' i+=2 else: result.append(s[i]) pattern += 'A' i+=1 elif s[i].isdigit(): antal = int(s[i]) i+=1 while i<len(s) and s[i].isdigit(): antal = antal*10 + int(s[i]) i+=1 result.append(str(antal)) 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>

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