Chemical calculator: Difference between revisions

From Rosetta Code
Content added Content deleted
No edit summary
No edit summary
Line 4: Line 4:
;Introduction
;Introduction


* A molecule consists of atoms. E.g. methane, CH4 has one carbon atom and four hydrogen atoms.
* 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
* The mass of water, H2O is 1.008 * 2 + 15.999 = 18.015
* An atom name consists of one or two letters, e.g. H (Hydrogen), He (Helium).
* An atom name consists of one or two letters, e.g. H (Hydrogen), He (Helium)
* The number of atoms is stated behind the atom or atom group.
* The number of atoms is stated behind the atom or atom group.
* An atom group is specified using parenthesis. Example: Butyric acid has two CH3 groups (CH3)2CHCOOH
* An atom group is specified using parenthesis. Example: Butyric acid has two CH3 groups (CH3)2CHCOOH

Revision as of 20:19, 18 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 or two letters, e.g. H (Hydrogen), He (Helium)
  • The number of atoms is stated behind the atom or atom group.
  • An atom group is specified using parenthesis. Example: Butyric acid has two CH3 groups (CH3)2CHCOOH
  • 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 and 12 electrons.
  • One mole of H2O has the mass 18.015 grams.
  • One mole is 6.02214076E23
Examples
  • 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')
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

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>