0

以前の投稿hereに続いて、提案されたことを実行し、コードを を使用して末尾再帰メソッドに変換しようとしましたlet

元のコード -動作しません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

コードが非常に読みづらく、かなり長いことは承知していますが、誰かが修正方法を説明してくれたり、この出力の理由を見つけるのを手伝ってくれたりすると、非常にありがたいです。

ありがとう

4

1 に答える 1

2

コードにいくつかの問題があります。

まず、 を使用lastしてリストの最後の要素を取得できます。詳細については、リストのドキュメントを参照してください。しかし、そうする本当に正当な理由がない限り、単純にリストの先頭から開始し、再帰的に先頭から要素をポップする方が簡単ではるかに効率的です。headパターン マッチングを使用して、コード内で最初の要素が既にバインドされています。

第二に、s を使用しない限りref(おそらく使用したくないでしょう)、標準 ML には変数はなく、値のみです。これが意味することは、呼び出し間で状態を保持したい場合、アキュムレータは関数のパラメータである必要があるということです。ヘルパー関数を使用してアキュムレータを初期化するのは一般的なパターンです。

[]3 番目に、リストを比較して空かどうかをテストする代わりに、null関数を使用します。これで私を信頼してください。=微妙な型推論の問題があるため、を使用すると警告が表示されます。さらに良いのは、関数のパラメーターでパターン マッチを使用するか、caseステートメントを使用することです。パターン マッチングにより、考えられるすべてのケースを処理したかどうかをコンパイラが判断できます。

第 4 に、SML は通常、変数名に snake_case ではなく camelCase を使用します。これはより文体的ですが、より多くのコードを書いて共同作業するにつれて、慣例に合わせたいと思うようになります.

第 5 に、リストで再帰を行うときは、リスト内の複数の値を調べようとしないでください。これは物事を複雑にします。これを head 要素と tail リストとして扱うと、すべてがはるかに簡単になります。私のコードでは、リストを最新の状態に保つ代わりに、別のパラメーターに分割することでこれを行いました。アキュムレータの 1 つから単に答えを返す基本ケースと、更新されたアキュムレータ値とリストからポップされた単一の値で再帰する再帰ケースがあります。これにより、問題のシナリオが解消されます。

何を計算しようとしているのかわからないので、このロジックが正しいかどうかはわかりませんが、私が話したことのいくつかを示すこのコードをチェックしてください.

(* 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 ループと同じくらい効率的です。

于 2012-12-29T23:46:50.497 に答える