Chemical calculator: Difference between revisions
Content added Content deleted
Alpha bravo (talk | contribs) (Added AutoHotkey) |
(Haskell version.) |
||
Line 2,000: | Line 2,000: | ||
C27H46O -> 386.664 |
C27H46O -> 386.664 |
||
Uue -> 315.000</pre> |
Uue -> 315.000</pre> |
||
=={{header|Haskell}}== |
|||
Create a set of parsers for molecular formulae and their subparts. The parsers maintain a running total of the mass parsed so far. Use a '''Reader''' monad to store a map from atom names to their masses. The contents of the map are read from the file '''chemcalc_masses.in''', not shown here. |
|||
<lang haskell>import Control.Monad (forM_) |
|||
import Control.Monad.Reader (Reader, ask, runReader) |
|||
import Data.Bifunctor (first) |
|||
import Data.Map (Map) |
|||
import qualified Data.Map as M |
|||
import Data.Void (Void) |
|||
import System.Environment (getArgs) |
|||
import System.IO (IOMode(ReadMode), withFile) |
|||
import System.IO.Strict (hGetContents) |
|||
import Text.Megaparsec (ParsecT, (<|>), between, errorBundlePretty, getOffset, |
|||
many, option, runParserT, some, setOffset) |
|||
import Text.Megaparsec.Char (char, lowerChar, upperChar) |
|||
import Text.Megaparsec.Char.Lexer (decimal) |
|||
import Text.Printf (printf) |
|||
type Masses = Map String Double |
|||
type ChemParser = ParsecT Void String (Reader Masses) Double |
|||
-- Parse the formula of a molecule, returning the latter's total mass. |
|||
molecule :: ChemParser |
|||
molecule = sum <$> some (atomGroup <|> atom) |
|||
-- Parse an atom group, optionally followed by its count, returning its total |
|||
-- mass. |
|||
atomGroup :: ChemParser |
|||
atomGroup = mul <$> between (char '(') (char ')') molecule <*> option 1 decimal |
|||
-- Parse an atom name, optionally followed by a count, returning its total mass. |
|||
atom :: ChemParser |
|||
atom = mul <$> atomMass <*> option 1 decimal |
|||
-- Parse an atom name, returning its mass. Fail if the name is unknown. |
|||
atomMass :: ChemParser |
|||
atomMass = do |
|||
off <- getOffset |
|||
masses <- ask |
|||
atomName <- (:) <$> upperChar <*> many lowerChar |
|||
case M.lookup atomName masses of |
|||
Nothing -> setOffset off >> fail "invalid atom name starting here" |
|||
Just mass -> return mass |
|||
-- Given a molecular formula and a map from atom names to their masses, return |
|||
-- the the total molar mass, or an error message if the formula can't be parsed. |
|||
molarMass :: String -> String -> Masses -> Either String Double |
|||
molarMass file formula = first errorBundlePretty . runChemParser |
|||
where runChemParser = runReader (runParserT molecule file formula) |
|||
-- Read from a file the map from atom names to their masses. |
|||
getMasses :: FilePath -> IO Masses |
|||
getMasses path = withFile path ReadMode (fmap read . hGetContents) |
|||
mul :: Double -> Int -> Double |
|||
mul s n = s * fromIntegral n |
|||
main :: IO () |
|||
main = do |
|||
masses <- getMasses "chemcalc_masses.in" |
|||
molecs <- getArgs |
|||
forM_ molecs $ \molec -> do |
|||
printf "%-20s" molec |
|||
case molarMass "<stdin>" molec masses of |
|||
Left err -> printf "\n%s" err |
|||
Right mass -> printf " %.4f\n" mass |
|||
</lang> |
|||
{{out}} |
|||
<pre> |
|||
H 1.0080 |
|||
H2 2.0160 |
|||
H2O 18.0150 |
|||
H2O2 34.0140 |
|||
(HO)2 34.0140 |
|||
Na2SO4 142.0355 |
|||
C6H12 84.1620 |
|||
COOH(C(CH3)2)3CH3 186.2950 |
|||
C6H4O2(OH)4 176.1240 |
|||
C27H46O 386.6640 |
|||
Uue 315.0000 |
|||
(HOz)2 |
|||
<stdin>:1:3: |
|||
| |
|||
1 | (HOz)2 |
|||
| ^ |
|||
invalid atom name starting here |
|||
</pre> |
|||
=={{header|Java}}== |
=={{header|Java}}== |