回溯标准ML

时间:2013-07-27 18:38:15

标签: standards sml backtracking ml

我在我的SML手册中看到了以下功能,它可以计算特定变化所需的特定种类的硬币数量。 例如change [5,2] 16 =[5,5,2,2,2]因为有2个5个硬币和3个2个硬币,所以有16个。

以下代码是一种回溯方法:

exception Change;
fun change _ 0 = nil|
    change nil _ = raise Change|
    change (coin::coins)=
if coin>amt then change coins amt
else (coin:: change (coin::coins) (amt-coin))

handle Change=> change coins amt;

它有效,但我不明白究竟是怎么回事。 我知道回溯是什么,我只是不明白这个特殊的功能。

到目前为止我所理解的:如果amt为0,则表示我们的更改已计算完毕,并且没有任何内容可以列入最终列表。

如果我们的'硬币列表'中没有更多硬币,我们需要返回一步。 这是我迷路的地方:如何提出异常有助于我们回去? 在我看来,处理程序试图调用change函数,但是“coins”参数不应该是nil吗?因此进入无限循环?为什么会“回去”;

最后一句对我来说非常明显:如果硬币值大于要改变的金额,我们使用剩余的硬币来构建变化。如果它小于剩余的数量,我们将其纳入结果列表。

3 个答案:

答案 0 :(得分:4)

最好通过写一个简单的例子来说明评估的进展情况。在每一步中,我只是在相应的右侧替换change的呼叫(为了更加清晰,我添加了额外的括号):

  change [3, 2] 4
= if 3 > 4 then ... else ((3 :: change [3, 2] (4 - 3)) handle Change => change [2] 4)
= (3 :: change [3, 2] 1) handle Change => change [2] 4
= (3 :: (if 3 > 1 then change [2] 1 else ...)) handle Change => change [2] 4
= (3 :: change [2] 1) handle Change => change [2] 4
= (3 :: (if 2 > 1 then change [] 1 else ...)) handle Change => change [2] 4
= (3 :: (raise Change)) handle Change => change [2] 4

此时已引发异常。它会冒泡到当前的处理程序,以便评估按如下方式进行:

= change [2] 4
= if 2 > 4 then ... else ((2 :: change [2] (4 - 2)) handle Change => change [2] 4)
= (2 :: change [2] 2) handle Change => change [2] 4
= (2 :: (if 2 > 2 then ... else ((2 :: change [2] (2 - 2)) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: ((2 :: change [2] 0) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: ((2 :: []) handle Change => change [2] 2)) handle Change => change [2] 4
= (2 :: (2 :: [])) handle Change => change [2] 4
= 2 :: 2 :: []

此处不再有失败,所以我们成功终止。

简而言之,每个处理程序都是一个回溯点。在每次失败(即,加注)时,您将进入最里面的处理程序,这是最后一个回溯点。设置每个处理程序本身,使其包含相应的调用来尝试。

答案 1 :(得分:3)

您可以使用'a option类型重写此异常使用。原来的功能:

exception Change;
fun change _ 0 = []
  | change [] _ = raise Change
  | change (coin::coins) amt =
    if coin > amt
    then change coins amt
    else coin :: change (coin::coins) (amt-coin)
         handle Change => change coins amt;

在下面的修改函数中,它不是异常冒泡,而是NONE。这里稍微明显的一件事是coin只出现在两种情况中的一种情况下(在上面的代码中总是会发生,但在回溯的情况下会被恢复)。

fun change' _ 0 = SOME []
  | change' [] _ = NONE
  | change' (coin::coins) amt =
    if coin > amt
    then change' coins amt
    else case change' (coin::coins) (amt-coin) of
             SOME result => SOME (coin :: result)
           | NONE        => change' coins amt

另一种展示所发生情况的方法是绘制一个调用树。这并没有像安德烈亚斯·罗斯伯格手工评估那样收集结果,但它确实表明只有时间change正在采用else - 分支才有可能回溯,并且如果发生回溯(即返回NONE或抛出异常),不要在结果中包含coin

  (original call ->)  change [2,5] 7
                      \ (else)
                       `-change [2,5] 5
                      /  \ (else)
  ___________________/    `-change [2,5] 3
 /                       /  \ (else)
/                       /    `-change [2,5] 1
`-change [5] 5         /       \ (then)
  \ (else)            /         `-change [5] 1
   `-change [] 0     /            \ (then)
     \              /              `-change [] 1
      `-SOME []     `-change [5] 3   \ (base)
                     \ (then)         `-NONE
                      `-change [] 3
                        \
                         `-NONE

答案 2 :(得分:0)

来源https://www.cs.cmu.edu/~rwh/introsml/core/exceptions.htm

<块引用>

表达式 exp handle match 是一个异常处理程序。这是 通过尝试评估 exp 来评估。如果它返回一个值,那么 那就是整个表达式的值;处理程序没有任何作用 在这种情况下。但是,如果 exp 引发异常 exn,则 异常值与匹配的子句匹配(正好 就像将子句函数应用于参数一样)到 决定如何进行。如果子句的模式匹配 异常 exn,然后评估继续使用的表达式部分 那个条款。如果没有模式匹配,异常 exn重新引发 所以 外部异常处理程序可能会调度它。如果没有处理程序 处理异常,然后未捕获的异常作为 评估的最终结果。也就是说,计算被中止 未捕获的异常exn

在更多操作方面,对 exp handle match 的评估通过 安装由 match 确定的异常处理程序,然后评估 经验。异常处理程序的先前绑定被保留,因此 一旦不再需要给定的处理程序,它就可以恢复。 引发异常包括将 exn 类型的值传递给 当前异常处理程序。将异常传递给处理程序 卸载该处理程序,并重新安装以前活动的 处理程序。这确保如果处理程序本身引发异常, 或未能处理给定的异常,则异常是 在评估 handle 之前传播到活动处理程序 表达。如果表达式没有引发异常,则 作为完成评估的一部分,恢复以前的处理程序 handle 表达式。