2015-04-21 3 views
3

Я пытался сделать химический состав парсера как упражнение для себя, но я застрял.Разбор химических соединений в Haskell

Вот тип данных Я пытаюсь использовать:

data Compound = Monoatomic String Int | Poliatomic [Compound] Int 

Учитывая строку, как «Ca (OH) 2», я хочу, чтобы получить что-то подобное;

Poliatomic [Monoatomic "Ca" 1, Poliatomic [Monoatomic "O" 1, Monoatomic "H" 1] 2 ] 1 

Конструктор моноатомного типа для одиночных атомов и полиатомный конструктор для нескольких атомов. В этом примере (OH) 2 представляет собой и внутреннюю полиатомную структуру, и она представлена ​​как Poliatomic [(Monoatomic O 1), (Monoatomic H 1)] 2. Число 2 означает, что у нас есть две из этих полиатомных структур.

Я сделал это много;

import Data.Char (isUpper) 
data Compound = Monoatomic String Int | Poliatomic [Compound] Int 

instance Functor Compound where 
     fmap f (Monoatomic s i) = Monoatomic (f s) i 
     fmap f (Poliatomic xs i) = Poliatomic (fmap f xs) i 

-- Change number of a compound 
changeNumber :: Compound -> Int -> Compound 
changeNumber (Monoatomic xs _) n = Monoatomic xs n 
changeNumber (Poliatomic xs _) n = Poliatomic xs n 

-- Take a partial compound and next chracter return partial compound 
parseCompound :: Compound -> Char -> Compound 
parseCompound (Poliatomic x:xs n) c 
     | isUpper c = Poliatomic ((Monoatomic [c] 1):x:xs) n -- add new atom to compound 
     | isLower c = Poliatomic 

-- I want to do foldl parseCompound (Poliatomic [] 1) inputstring 

, но потом мне стало слишком сложно продолжать.

Похоже, что это должна быть довольно простая проблема, но я очень новичок в Haskell и не могу понять, как выполнить эту функцию.

У меня есть эти вопросы:

  • мой подход правильно до сих пор?
  • Как я могу сделать эту работу?
+7

Знаете ли вы о комбинаторах парсеров, таких как [parsec] (https://hackage.haskell.org/package/parsec) и [attoparsec] (https://hackage.haskell.org/package/attoparsec)? Они действительно помогают облегчить писать парсеры! Я действительно понимаю, что вы, возможно, захотите сделать что-то совершенно самостоятельно для чего-то такого размера. – gspr

+0

Я только что их слышал, но я не знаю, как их использовать, или что именно они делают – yasar

+0

Есть хороший [pdf] (http://research.microsoft.com/en-us/um/people/daan /download/parsec/parsec.pdf), и вы всегда можете посмотреть [на haskellWiki] (https://wiki.haskell.org/Parsec) (бот некоторые ссылки там сломаны) – Carsten

ответ

4

Я создал анализатор, который вы ищете с Parsec, чтобы дать вам ощущение того, что парсек парсеры выглядеть, так как вы заявили, вы имели небольшой опыт работы с ним.

Даже с небольшим опытом Haskell, он должен быть достаточно читабельным. Я предоставил некоторые комментарии по тем частям, в которых есть что-то особенное, чтобы искать.

import Text.Read (readMaybe) 
import Data.Maybe (fromMaybe) 
import Text.Parsec (parse, many, many1, digit, char, string, (<|>), choice, try) 
import Text.Parsec.String (Parser) 


data Compound 
    = Monoatomic String Int 
    | Poliatomic [Compound] Int 
    deriving Show 


-- Run the substance parser on "Ca(OH)2" and print the result which is 
-- Right (Poliatomic [Monoatomic "Ca" 1,Poliatomic [Monoatomic "O" 1,Monoatomic "H" 1] 2] 1) 
main = print (parse substance "" "Ca(OH)2") 


-- parse the many parts which make out the top-level polyatomic compound 
-- 
-- "many1" means "at least one" 
substance :: Parser Compound 
substance = do 
    topLevel <- many1 part 
    return (Poliatomic topLevel 1) 


-- a single part in a substance is either a poliatomic compound or a monoatomic compound 
part :: Parser Compound 
part = poliatomic <|> monoatomic 


-- a poliatomic compound starts with a '(', then has many parts inside, then 
-- ends with ')' and has a number after it which indicates how many of it there 
-- are. 
poliatomic :: Parser Compound 
poliatomic = do 
    char '(' 
    inner <- many1 part 
    char ')' 
    amount <- many1 digit 
    return (Poliatomic inner (read amount)) 


-- a monoatomic compound is one of the many element names, followed by an 
-- optional digit. if omitted, the amount defaults to 1. 
-- 
-- "try" is a little special, and required in this case. it means "if a parser 
-- fails, try the next one from where you started, not from where the last one 
-- failed." 
-- 
-- "choice" means "try all parsers in this list, stop when one matches" 
-- 
-- "many" means "zero or more" 
monoatomic :: Parser Compound 
monoatomic = do 
    name <- choice [try nameParser | nameParser <- atomstrings] 
    amount <- many digit 
    return (Monoatomic name (fromMaybe 1 (readMaybe amount))) 


-- a list of parser for atom names. it is IMPORTANT that the longest names 
-- come first. the reason for that is that it makes the parser much simpler to 
-- write, and it can execute much faster. it's common when designing parsers to 
-- consider things like that when creating them. 
atomstrings :: [Parser String] 
atomstrings = map string (words "He Li Be Ne Na Mg Al Ca H B C N O F") 

Я попытался написать этот код таким образом, что должно быть по крайней мере, достаточно доступным для новичка, но это, вероятно, не кристально ясно, так что я буду рад ответить на любые вопросы по этому поводу.


Парсер выше - тот, который вы хотели. Однако это не тот, который я бы написал, если бы у меня были свободные вожжи. Если я должен сделать, однако я хотел, я бы использовать тот факт, что

Ca(OH)2 

может быть представлена ​​как

(Ca)1((O)1(H)1)2 

который является гораздо более равномерное представление, и в свою очередь приводит к более простой структуры данных и парсер с меньшим количеством шаблонов. Код, который я бы предпочел, чтобы писать будет выглядеть  

import Text.Read (readMaybe) 
import Data.Maybe (fromMaybe) 
import Control.Applicative ((<$>), (<*>), pure) 
import Text.Parsec (parse, many, many1, digit, char, string, (<|>), choice, try, between) 
import Text.Parsec.String (Parser) 


data Substance 
    = Part [Substance] Int 
    | Atom String 
    deriving Show 


main = print (parse substance "" "Ca(OH)2") 
-- Right (Part [Part [Atom "Ca"] 1,Part [Part [Atom "O"] 1,Part [Atom "H"] 1] 2] 1) 

substance :: Parser Substance 
substance = Part <$> many1 part <*> pure 1 

part :: Parser Substance 
part = do 
    inner <- polyatomic <|> monoatomic 
    amount <- fromMaybe 1 . readMaybe <$> many digit 
    return (Part inner amount) 

polyatomic :: Parser [Substance] 
polyatomic = between (char '(') (char ')') (many1 part) 

monoatomic :: Parser [Substance] 
monoatomic = (:[]) . Atom <$> choice (map (try . string) atomstrings) 

atomstrings :: [String] 
atomstrings = words "He Li Be Ne Na Mg Al Ca H B C N O F" 

Это использует несколько «продвинутые» трюки в Haskell (такие, как <$> и <*> операторов), поэтому не может представлять интерес для вас, ОП, но Я вкладываю его в других людей, которые могут быть более продвинутыми пользователями Haskell и узнавать о Parsec.

Этот анализатор занимает примерно половину страницы, как вы видите, и это мощь библиотек, таких как Parsec, - они делают это и легким, и fun для написания парсеров!

Смежные вопросы