【问题标题】:Parsing Karva notation in haskell在haskell中解析Karva符号
【发布时间】:2012-08-08 16:42:33
【问题描述】:

Karva 符号在基因表达编程中用于表示数学表达式。

请看这里http://www.gene-expression-programming.com/Tutorial002.asp

您通过读取基因并从左到右、从上到下填充节点来创建表达树。

因此,例如在 "+*+1+2*3456" 中使用运算符 (+, *) 和终结符 (1,2,3,4,5,6) 将计算为 39。

我将如何使用 attoparsec(或 parsec)在 haskell 中执行此操作?

karvaParser :: Parser Int
karvaParser = ????????????

Prelude> parse karvaParser "+*+1+2*3456"
Done 39

【问题讨论】:

  • 你读过任何关于秒差的教程吗?这篇文章可能会被关闭,因为您的问题实际上是“我如何使用 parsec?”。
  • 嗯,我了解基础知识,并认为这将是微不足道的。但它一直困扰着我。只是在寻找一些见解或提示。这是一个关于如何解析 karva 表示法的合理问题。
  • 在这里看看我的相关答案:stackoverflow.com/a/6963984/125421
  • 请不要让我们为您编写代码。如果您需要的话,有许多优秀的在线资源可供您开始使用 Parsec。如果没有,请告诉我们您尝试了什么,并告诉我们为什么它不起作用。
  • 剧透:parsec 并不是像 Karva 表示法那样解析广度优先树的最简单方法。查看上面@NathanHowell 评论中的链接。

标签: haskell parsec attoparsec


【解决方案1】:

(我已经在this answer 中证明了对于 cmets 中提到的问题,这是一个线性时间算法。在这个答案的previous revision 中有一个更长更手动的解决方案。)

基因表达编程:Karva 表示法。

使用延续传递单子Cont 可能有一个巧妙的解决方案,但我没有想到它。这是该问题的一个相当干净的纯功能解决方案。我会借此机会列举一些好的通用递归方案。

计划:

  1. 使用前一行的总数量将输入分成列表,每层一个。这是一个变形,即从种子 ([]) 生成一个列表,可以使用 unfoldr :: (b -> Maybe (a, b)) -> b -> [a] 或等效的 unfoldr' :: (b -> (a, b)) -> (b -> Bool)-> b -> [a] 编写。

    input:   "Q/a*+b-cbabaccbac"
    arities:  12022020000000000
    output:  ["Q","/","a*","+b","-c","ba"]
    
  2. 递归地使用splitAt 将子级粘合在父级之下。这是一种变态,即将列表折叠成单个(树)值,可以使用foldr :: (a -> b -> b) -> b -> [a] -> b

  3. 编写
  4. 将变形和变形合二为一。这就是所谓的hylomorphism。 这些术语在开创性论文 Functional Programming with Bananas, Lenses and Barbed wire 中介绍给 FP 社区。​​p>

代码

如果您不熟悉它,Data.Tree 提供 data Tree a = Node {rootLabel :: a, subForest :: Forest a} 其中type Forest a = [Tree a]

import Data.Tree
import Data.Tree.Pretty -- from the pretty-tree package

arity :: Char -> Int
arity c 
  | c `elem` "+*-/" = 2
  | c `elem` "Q" = 1
  | otherwise = 0

hylomorphism :: b -> (a -> b -> b) -> (c -> (a, c)) -> (c -> Bool) -> c -> b
hylomorphism base combine pullout stop seed = hylo seed where
 hylo s | stop s = base
        | otherwise = combine new (hylo s') 
          where (new,s') = pullout s

为了抽出一个关卡,我们使用上一个关卡的总数量来找到在哪里拆分这个新关卡,然后将这个关卡的总数量传递给下一次准备:

pullLevel :: (Int,String) -> (String,(Int,String))
pullLevel (n,cs) = (level,(total, cs')) where
                   (level,        cs') = splitAt n cs
                   total = sum $ map arity level

要将关卡(作为字符串)与下面的关卡(已经是森林)结合起来,我们只需提取每个角色需要的树的数量。

combineLevel :: String -> Forest Char -> Forest Char
combineLevel "" [] = []
combineLevel (c:cs) levelBelow = Node c subforest : combineLevel cs theRest 
      where (subforest,theRest) = splitAt (arity c) levelBelow

现在我们可以使用 hylomorphism 解析 Karva。请注意,我们使用来自1 字符串之外的全部数量来播种它,因为在根级别只有一个节点。我使用了head 函数,因为1 导致顶层是一个包含一棵树的列表。

karvaToTree :: String -> Tree Char
karvaToTree cs = let
  zero (n,_) = n == 0          
    in head $ hylomorphism [] combineLevel pullLevel zero (1,cs) 

演示

让我们画出结果(因为 Tree 的语法如此之多,以至于很难阅读输出!)。你必须cabal install pretty-tree 才能得到Data.Tree.Pretty

see :: Tree Char -> IO ()
see = putStrLn.drawVerticalTree.fmap (:"")
ghci> arity '+'
2

ghci> pullLevel (3,"+a*bc/acb")
("+a*",(4,"bc/acb"))

ghci> combineLevel "a*" [Node 'b' [],Node 'c' []]
[Node {rootLabel = 'a', subForest = []},Node {rootLabel = '*', subForest = [Node {rootLabel = 'b', subForest = []},Node {rootLabel = 'c', subForest = []}]}]

ghci> see . Node '.' $ combineLevel "a*" [Node 'b' [],Node 'c' []]
   .   
   |   
 ---   
/   \  
a   *  
    |  
    -- 
   /  \
   b  c

ghci> karvaToTree "Q/a*+b-cbabaccbac"
Node {rootLabel = 'Q', subForest = [Node {rootLabel = '/', subForest = [Node {rootLabel = 'a', subForest = []},Node {rootLabel = '*', subForest = [Node {rootLabel = '+', subForest = [Node {rootLabel = '-', subForest = [Node {rootLabel = 'b', subForest = []},Node {rootLabel = 'a', subForest = []}]},Node {rootLabel = 'c', subForest = []}]},Node {rootLabel = 'b', subForest = []}]}]}]}

哪些匹配
正如我们在see它时看到的那样:

ghci> see $ karvaToTree "Q/a*+b-cbabaccbac"
      Q      
      |      
      /      
      |      
 ------      
/      \     
a      *     
       |     
       ----- 
      /     \
      +     b
      |      
     ----    
    /    \   
    -    c   
    |        
    --       
   /  \      
   b  a  

评估

一旦你有了树,就很容易将它转换成其他东西。让我们用 Karva 表示法计算一个表达式:

action :: (Read num,Floating num) => Char -> [num] -> num
action c = case c of
   'Q' -> sqrt.head
   '+' -> sum
   '*' -> product
   '-' -> \[a,b] -> a - b
   '/' -> \[a,b] -> a / b
   v -> const (read (v:""))

eval :: (Read num,Floating num)  => Tree Char -> num
eval (Node c subforest) = action c (map eval subforest)
ghci> see $ karvaToTree "Q+-*826/12"
      Q      
      |      
      +      
      |      
  -------    
 /       \   
 -       *   
 |       |   
 --    ---   
/  \  /   \  
8  2  6   /  
          |  
          -- 
         /  \
         1  2

ghci> eval $ karvaToTree "Q+-*826/12"
3.0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 1970-01-01
    • 2017-10-22
    • 1970-01-01
    • 2012-06-29
    • 1970-01-01
    • 2018-05-14
    相关资源
    最近更新 更多