将分形树函数从递归转换为迭代

问题描述

我在sml中编写了以下程序,以将分形树生成到网页中(我对此感到非常自豪?)。我使用了递归函数,因为sml中没有for循环。

val width = 1000.0;
val height = 1000.0;
val length = 350.0;
val scale = 1.0 / 1.618;
val header = "<!DOCTYPE html><html><body><svg width='1000' height='1000' version='1.1' xmlns='http://www.w3.org/2000/svg'> \n";
val footer = "</svg></body></html>";
val outfile = TextIO.openOut "fractal-tree.html";

fun tree(x,y,length,angle) =
    if length > 1.0 then (
        let
            val x2 = x + length * (Math.cos angle)
            val y2 = y + length * (Math.sin angle)
            val outString = "<line x1='" ^ Real.toString x ^ "'"
            ^ " y1='" ^ Real.toString y ^ "'"
            ^ " x2='" ^ Real.toString x2 ^ "'"
            ^ " y2='" ^ Real.toString y2 ^ "'"
            ^ " style='stroke:dodgerblue;stroke-width:2'/> \n"
        in
            TextIO.output(outfile,outString);
            tree(x2,y2,length * scale,angle + Math.pi / 2.5);
            tree(x2,angle - Math.pi / 2.5)
        end
    )else ();
    
    
TextIO.output(outfile,header);
tree(width / 2.0,height,3.0 * Math.pi / 2.0);
TextIO.output(outfile,footer);
TextIO.closeOut outfile;

现在,我需要一些需要更改的代码才能将其转换为迭代函数。

这只是次要的,但是我想知道是否有一种方法可以在控制台中显示我的树。当我尝试时,我只会得到一个实数序列,然后程序停止。

解决方法

做得好!

我需要一些需要更改的代码才能将其转换为迭代函数。

我确定了您代码的三个部分:

  1. 生成SVG字符串的部分
  2. 与文件交互的部分
  3. 递归调用的部分

我将这些部分分开,以便您可以随意组合它们,而无需彼此依赖。例如,您可以组成分形效果而不必组成文件系统效果。

关于迭代与递归,由于您使用的是功能语言,因此我建议继续递归。但是,因为您的递归模式是树形的(每个调用可能会创建另外两个调用),所以递归可能会溢出堆内存。

因此,迭代解决方案和 tail-recursive 解决方案将实现相同的结果:由于您同时向左和向右递归,但不能同时递归,因此可以使用显式而不是隐式存储堆栈,您稍后也将以其他方式递归。


这里是重写;我将在下面评论:

datatype Line = Line of { x1 : real,y1 : real,x2 : real,y2 : real }
datatype Seed = Seed of { x : real,y : real,length : real,angle : real }

val scale = 1.0 / 1.618
val initialAngle = 3.0 * Math.pi / 2.0

fun makeLine (Seed { x = x1,y = y1,length = length,angle = angle }) =
    let
      val x2 = x1 + length * (Math.cos angle)
      val y2 = y1 + length * (Math.sin angle)
    in
      Line { x1 = x1,y1 = y1,x2 = x2,y2 = y2 }
    end

fun makeSplits (Line line) (Seed seed) =
    let
      val newx = #x2 line
      val newy = #y2 line
      val newlen = #length seed * scale
      val left = Seed { x = newx,y = newy,length = newlen,angle = (#angle seed) + Math.pi / 2.5 }
      val right = Seed { x = newx,angle = (#angle seed) - Math.pi / 2.5 }
    in
      [ left,right ]
    end

fun isBelowThreshold (Seed { length = derp,... }) =
    derp < 1.0

fun makeLines seeds =
    let
      fun go [] lines = lines
        | go (seed :: stack) lines =
          if isBelowThreshold seed
          then go stack lines
          else let
            val line = makeLine seed
            val splits = makeSplits line seed
          in
            go (splits @ stack) (line :: lines)
          end
    in
      go seeds []
    end

val toStr = Real.toString

fun drawLine (Line { x1 = x1,y2 = y2 }) =
    String.concat
      [ "<line x1='",toStr x1,"'"," y1='",toStr y1," x2='",toStr x2," y2='",toStr y2," style='stroke:dodgerblue;stroke-width:2'/>\n"
      ]

fun drawLines lines =
    String.concat (List.map drawLine lines)

fun drawFractal width height length =
    let
      val header = String.concat
        [ "<!DOCTYPE html><html><body>","<svg width='",toStr width," height='",toStr height," version='1.1' xmlns='http://www.w3.org/2000/svg'>\n"
        ]
      val initialSeed = Seed {
            x = width / 2.0,y = height,angle = initialAngle
          }
      val lines = makeLines [ initialSeed ]
      val footer = "</svg></body></html>"
    in
      String.concat [ header,drawLines lines,footer ]
    end

fun writeToFile path s =
    let val fd = TextIO.openOut path
    in TextIO.output (fd,s)
     ; TextIO.closeOut fd
    end

因此,LineSeed数据类型不是严格必需的:您还可以使用四元组或未用{{1}包装的{ ... }记录},例如使用以下类型别名之一:

datatype

但是要点是(1)将递归的行和起点表示为与SVG或文件系统操作无关的抽象事物,以及(2)通过命名来区分一个4元组与另一个4元组。 >

将它们包装在type Line = { x1 : real,y2 : real } type Seed = real * real * real * real 中而不是类型别名中的一件好事是,这些类型将在REPL和错误消息中显示为datatypeLine。这使得两者之间的区别

Seed

我敢肯定有一种更简单的方法,其中从val makeLines = fn : (real * real * real * real) list -> (real * real * real * real) list val makeLines = fn : Seed list -> Line list 推断Seed中的角度,但是由于我对分形的有限的机械理解,至少我对“ part”有两个名字。的结果”({{1)}和“递归的一部分”(Line)。

对于递归,您的Line函数被命名为Seed,并且要实现尾递归,将使内部函数tree只要具有{{1 }}(称为makeLines)。每个go会生成一个seeds和一些新种子;哪些移动到seed :: stack中,只是为了澄清seed主要是进行递归。

line(或您的makeSplits)是否因所有输入而终止取决于某些几何推理。不是数学家,这不是那么令人安慰。但是至少,如果您犯了一个错误,此时程序将耗尽内存,并且不会用分形填充硬盘。 :-D

改善的想法:

  • 我想看看这棵树的一个版本,它可以随机选择两个或三个分支。这将涉及调用go中的伪随机数生成器,并生成当前的两个分割或具有不同角度的三个分割。
  • 您可以在中心开始进行分形,然后将其分为3、4、5或6方向进行分割以制作雪花。但是看来,按照目前的星座,其中一些星座需要较小的初始长度以避免重叠。
  • 分支机构可以根据其父对象对其颜色进行轻微的突变。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...