我也可以转换可变的,只有算法的单任务,仍然是有效的?我也、仍然是、算法、有效

2023-09-11 00:15:33 作者:當前狀態錄取

这个问题的背景是,我想玩弄基因防爆pression编程(GEP),进化算法的一种形式,使用二郎。 GEP利用基于字​​符串的DSL名为 Karva符号的'的。 Karva符号很容易转化为EX pression分析树,但翻译算法假定有一个实现可变对象:不完全分前pressions创建早在翻译过程中和自己分前pressions被填充在后面,与以前不曾知道的时间值,他们创建。

The context of this question is that I want to play around with Gene Expression Programming (GEP), a form of evolutionary algorithm, using Erlang. GEP makes use of a string based DSL called 'Karva notation'. Karva notation is easily translated into expression parse trees, but the translation algorithm assumes an implementation having mutable objects: incomplete sub-expressions are created early-on the translation process and their own sub-expressions are filled-in later-on with values that were not known at the time they were created.

Karva符号的目的是,它保证没有任何昂贵的编码技术或遗传code改正创建语法正确的前pressions。问题是,与单任务编程语言像二郎,我必须重新前pression树不断地为每个子EX pression得到填补这需要一个便宜 - O(N)? - 更新操作,并把它转换成一个会在指数时间完成(除非我错了)。如果我不能找到一个有效的功能算法转换成K-EX pressions进入前pression树,那么GEP引人注目的特点是少了一个。

The purpose of Karva notation is that it guarantees syntactically correct expressions are created without any expensive encoding techniques or corrections of genetic code. The problem is that with a single-assignment programming language like Erlang, I have to recreate the expression tree continually as each sub expression gets filled in. This takes an inexpensive - O(n)? - update operation and converts it into one that would complete in exponential time (unless I'm mistaken). If I can't find an efficient functional algorithm to convert K-expressions into expression trees, then one of the compelling features of GEP is lost.

我AP preciate的K-EX pression翻译的问题是pretty的晦涩难懂,所以我要的是如何转换的内在,非功能性的算法(ALG意见,利用可变数据结构)到一个不。如何做纯函数式编程语言适应许多依赖于可变性来获得他们所需要的性能特性产生于计算机科学的早期的算法和数据结构的?

I appreciate that the K-expression translation problem is pretty obscure, so what I want is advice on how to convert an inherently-non-functional algorithm (alg that exploits mutable data structures) into one that does not. How do pure functional programming languages adapt many of the algorithms and data structures that were produced in the early days of computer science that depend on mutability to get the performance characteristics they need?

推荐答案

不可变的数据结构只是一个效率的问题,如果他们是不断变化的,或者你建立他们走错了路。例如,连续地追加更向越来越列表的末尾是二次的,而串联列表的列表是线性的。如果你仔细想想,你通常可以建立你的结构,一个合理的方式,和懒惰的评价是你的朋友 - 伸手承诺去解决它,并停止担心

Carefully designed immutability avoids unecessary updating

Immutable data structures are only an efficiency problem if they're constantly changing, or you build them up the wrong way. For example, continually appending more to the end of a growing list is quadratic, whereas concatenating a list of lists is linear. If you think carefully, you can usually build up your structure in a sensible way, and lazy evaluation is your friend - hand out a promise to work it out and stop worrying.

盲目尝试复制势在必行算法可以ineffecient,但是你错了你的说法,函数式编程已经在这里渐近不好。

Blindly trying to replicate an imperative algorithm can be ineffecient, but you're mistaken in your assertion that functional programming has to be asymptotically bad here.

我会坚持你的情况分析Karva记法GEP的研究。 ( 我已经与这个解决方案更全面的this回答。)

I'll stick with your case study of parsing Karva notation for GEP. ( I've played with this solution more fully in this answer.)

下面是一个相当干净纯粹的功能性问题的解决方案。我要抓住这个机会来命名下降一些很好的通用递归计划前进的道路上。

Here's a fairly clean pure functional solution to the problem. I'll take the opportunity to name drop some good general recursion schemes along the way.

(导入 Data.Tree 用品数据树A = {节点:: rootLabel一,subForest ::森林一} 其中,键入森林A = [树一]

import Data.Tree
import Data.Tree.Pretty -- from the pretty-tree package for visualising trees

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

一个hylomorphism是anamorphism的组成(建立,unfoldr)和catamorphism(结合,foldr相似)。 这些术语引入到FP社区中的开创性论文函数式编程香蕉,镜头和铁丝网。

A hylomorphism is the composition of an anamorphism (build up, unfoldr) and a catamorphism (combine, foldr). These terms are introduced to the FP community in the seminal paper Functional Programming with Bananas, Lenses and Barbed wire.

我们要去拉出来的水平(ANA /展开),并结合他们重新走到一起(CATA /倍)。

We're going to pull the levels out (ana/unfold) and combine them back together (cata/fold).

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

要拿出一个水平,我们使用了previous水平的总元数来找到在哪里分裂了这个新的水平,并通过对总元数为这一个准备下一次:

To pull out a level, we use the total arity from the previous level to find where to split off this new level, and pass on the total arity for this one ready for next time:

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

要合并的水平(字符串)与下面的水平(这已经是一个森林),我们只是拉过的每一个字符需要一个树的数量。

To combine a level (as a String) with the level below (that's already a Forest), we just pull off the number of trees that each character needs.

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 ,因为有根级别只有一个节点。相应地,我们运用的结果得到这个单背出hylomorphism后。

Now we can parse the Karva using a hylomorphism. Note that we seed it with a total arity from outside the string of 1, since there's only one node at the root level. Correspondingly we apply head to the result to get this singleton back out after the hylomorphism.

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

线性时间

有没有指数爆破,也没有重复的为O(log(n))的查找或昂贵的修改,所以我们不应该在太麻烦了。

Linear Time

There's no exponential blowup, nor repeated O(log(n)) lookups or expensive modifications, so we shouldn't be in too much trouble.

元数为O( 1 splitAt部分为O(部分 pullLevel(一部分,CS)为O(部分)抓斗使用 splitAt 获得水平,以及O(部分)的图元数级,所以O(部分 combineLevel(C:CS)为O(元数ç)的 splitAt 和O(总和$图元数CS )的递归调用

hylomorphism [] combineLevel pullLevel零(1,CS) arity is O(1) splitAt part is O(part) pullLevel (part,cs) is O(part) for grab using splitAt to get level, plus O(part) for the map arity level, so O(part) combineLevel (c:cs) is O(arity c) for the splitAt, and O(sum $ map arity cs) for the recursive call 基于SINR和滞留时间的垂直切换算法

hylomorphism [] combineLevel pullLevel zero (1,cs) 使 pullLevel 要求每个级别,所以总 pullLevel 成本为O(部分)= O(N) 使 combineLevel 要求每个级别,所以总 combineLevel 成本为O(总和$图元数水平)= O(N),因为在整个输入的总元数必然由n个为有效字符串。 在使O(#levels)调用(这是O( 1 ))和 #levels 被绑定 N ,所以这是低于O(n)的太 makes a pullLevel call for each level, so the total pullLevel cost is O(sum parts) = O(n) makes a combineLevel call for each level, so the total combineLevel cost is O(sum $ map arity levels) = O(n), since the total arity of the entire input is bound by n for valid strings. makes O(#levels) calls to zero (which is O(1)), and #levels is bound by n, so that's below O(n) too

因此​​ karvaToTree 是线性输入的长度。

Hence karvaToTree is linear in the length of the input.

我认为,提出要休息,你需要使用的可变性在这里得到一个线性算法的说法。

I think that puts to rest the assertion that you needed to use mutability to get a linear algorithm here.

让我们的结果平局(因为树是如此充满语法很难读取输出!)。你必须小集团安装pretty的树获得 Data.Tree。pretty的

Let's have a draw of the results (because Tree is so full of syntax it's hard to read the output!). You have to cabal install pretty-tree to get Data.Tree.Pretty.

see :: Tree Char -> IO ()
see = putStrLn.drawVerticalTree.fmap (:"")

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 = []}]}]}]}

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

从本教程中,我发现的例子< /一>:

which matches the output expected from this tutorial where I found the example: