SML中的尾递归不会显示任何输出

时间:2012-12-29 16:50:58

标签: recursion programming-languages functional-programming sml smlnj

按照我之前发布的帖子here,我尝试按照建议进行操作并转换代码 使用let进入Tail-recursion方法。

原始代码 - 不起作用(由于在val条件中使用if):

fun func() = 

val decimal = 0 (* the final result *)
val multiple = 0 (* keeps track of multiples, eg. In XXV, X would be a multiple *)
val current = 0 (* the digit currently being processed *)
val top = 0   (* value of the last element in the list *)
val last_add = 0 (* the last digit that wasn't a multiple, or subtraction operation *)
val last_sub = 0
val problem = 0 (* if value is 1 then there is a problem with the input *)
val myList = [1,2,3,4,5] (* the list has more values *)

while (myList <> [])    (* run while the list is not empty *)

    val current = tl(myList) (* grab the last element from the list *)
    val myList = tl(myList) (* remove the last element from the list *)
    val top = tl(myList) (* grab the value at the end of the list *)
    if ( myList <> []) andalso (current > top))
       then      

                val decimal = decimal + current - top
                val last_sub = top;
                val myList = tl(myList)
       else     
           if ( (myList = []) andalso (current = top))
              then val decimal = decimal + current
                   val multiple = multiple + 1
              else
                  if (last_sub = current)
                     then val problem = 1

                     else
                          val decimal = decimal + current
                          val multiple = 0
                          val last_add = current

代码作为尾递归方法:

fun calc [] = 0
    |calc [x] = x
    |calc (head::tail) = 
       let 
          val decimal = 0
          val multiple = 0
          val current = 0
          val top = 0  
          val last_add = 0
          val last_sub = 0
          val problem = 0  
          val doNothing = 0
       in      

          let
              val current = hd(rev(head::tail))  (* grab the last element *) 
              val head::tail = rev(tl(rev(head::tail)))  (* POP action - remove the last element from the list *)
              val top = hd(rev(head::tail))      (* grab the new last element after removing *)
              in 
                if (current > top) then 
                    let 
                          val decimal = decimal + current - top
                          val last_sub = top 
                          val head::tail = rev(tl(rev(head::tail)))  (* POP action - remove the last element from the list *)
                    in
                    calc(head::tail)
                    end
                else
                 if ( (head::tail = []) andalso (current = top))
                   then let 
                          val decimal = decimal + current
                          val multiple = multiple + 1
                        in 
                          calc(head::tail)
                        end
                 else 
                     if (last_sub <> current)
                       then let 
                               val decimal = decimal + current
                               val multiple = 0
                               val last_add = current
                            in 
                               calc(head::tail)
                            end
                     else
                        (* do nothing *)    
                        val doNothing = 0
               end      

       end; 

但是,当我尝试输入时:

calc([0,100,20,30,4,50]);

我明白了:

uncaught exception Bind [nonexhaustive binding failure]
  raised at: stdIn:216.13-216.50

我知道代码很难阅读并且很长,但我们将非常感激 如果有人能向我解释如何修复它,或者帮我找到输出的原因。

谢谢

1 个答案:

答案 0 :(得分:2)

您的代码存在一些问题。

首先,您可以使用last来获取列表的最后一个元素。有关详细信息,请参阅List documentation。但除非你有充分的理由这样做,否则从列表的开头简单地开始并且在你开始时从头开始弹出元素会更容易,也更有效率。您已经使用模式匹配在代码中绑定了head的第一个元素。

其次,除非您使用ref(您可能不想这样做),否则标准ML中没有变量,只有值。这意味着如果你想在调用之间携带状态,任何累加器都需要是你函数的参数。使用辅助函数初始化累加器是一种常见的模式。

第三,不要将列表与[]进行比较以测试它是否为空,而是使用null函数。相信我。由于微妙的类型推断问题,您将使用=收到警告。更好的是,在函数的参数上使用模式匹配或使用case语句。模式匹配允许编译器告诉您是否已处理所有可能的情况。

第四,SML通常使用camelCase而不是snake_case来表示变量名。这更具风格,但是当您编写更多代码并进行协作时,您将希望符合惯例。

第五,当你在列表上进行递归时,不要试图查看列表中的多个值。这使事情复杂化。将它视为头部元素和尾部列表,一切都将变得更加简单。在我的代码中,我没有在列表中保持当前状态,而是将其拆分为单独的参数。有一个基本情况,你只需要从你的一个累加器中返回答案,以及一个递归的情况,你可以用更新的累加器值和从列表中弹出的单个值进行递归。这消除了问题情况。

我不确定这个逻辑是否正确,因为我不知道你想要计算什么,但查看这段代码,说明我谈到的一些事情。

(* This is the helper function which takes accumulators as
   parameters. You shouldn't call this directly. *)
fun calc' decimal _ _ _ _ [] =
    (* We processed everything in the list.  Just return the accumulator. *)
    decimal
  | calc' decimal multiple lastAdd lastSub current (top::tail) =
    (* This case is for when there are 1 or more elements in the list. *)
    if current > top then
        calc' (decimal + current - top) multiple lastAdd top top tail
    else if current = top then
        calc' (decimal + current) (multiple + 1) lastAdd lastSub top tail
    else
        calc' (decimal + current) 0 current lastSub top tail

(* This is the function you should call. *)
fun calc [] = 0
  | calc [_] = 0 (* Given a single-element list. *)
  | calc (x::xs) =
    (* Apply the helper with correct initial values. *)
    calc' 0 0 0 0 x xs

在函数式语言中,只需递归并指定正确参数的新值,而不是在想要更改变量时分配给变量。这就是你使用递归在函数式语言中编写“循环”的方法。只要您只使用尾递归,它就会像您最喜欢的命令式语言中的while循环一样高效。