この再帰コードを理解する方法は?


12

入力された行数に基づいて小石の数を見つける関数An Introduction to Programming in Emacs Lispの助けを借りて再帰を実証するマニュアルでこのコードを見つけました。condそこ。

(defun triangle-using-cond (number)
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

引数4を渡した後、10と評価されます:

(triangle-using-cond 4)

マニュアルでは、特にこのサンプルコードの各ステップで何が起こるかを明確に説明していなかったため、ここで再帰がどのように機能するかを理解できませんでした。各インスタンスで何が起こるかをステップごとにメカニックスを理解するのに役立ちますか?


私は他の誰かに「再帰」という言葉を手伝ってもらおう(この文脈では違うと思うので)、または私がこれから書きたいことをもっとよく説明する:(a)数が以下の場合0に、次に0。(b)数値が1に等しい場合、1。(c)数値が1より大きい場合、関数triangle-using-condが返す値に数値を加算します。引数は数値より1小さい値です。条件は、a、b、cの順になります。最初に一致するものは何でも、降圧が停止します。
法律家14年

通常のように、elispインタプリタは最も内側から外側に向かって評価します。このように、1-4 = 3。これで再帰呼び出しはになりますが(triangle-using-cond 3)、条件1に到達するまで何度も同じ再帰呼び出しになりますか?次は何が起こるのだろう?
博士号

ああ、わかりました-関数はステップ3でそれ自体を再利用します-わかりました、良い点
法律家14年

私は何の結果になるのだろうか(triangle-using-cond 3)
博士号取得14年

2
nb 1-特に呼び出しを中置記法であるかのように読む場合、この関数には特に誤解を招く名前があります。引数から1を引いた値を返します。1から引数を引いたものではありません。
フィル14年

回答:


14

「printfデバッグ」を使用する

関数定義を変更することで、Emacsに理解を助けてもらうことができます。

(defun triangle-using-cond (number)
  (message (format "called with %d" number))
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

バッファーに(message ...)証跡を出力する場所を追加してください*Messages*

Edebugを使用する

関数定義内の任意の場所にポイントを置き、ヒットC-u C-M-xして「計測」します。そして、後にポイントを置くことによって、機能、例えばを評価(triangle-using-cond 3)して打ちますC-x C-e

これで、Edebugモードになりました。スペースバーを押して、機能をステップスルーします。各式の中間値がエコー領域に表示されます。Edebugモードを終了するには、を押してくださいq。インストルメンテーションを削除するには、定義内の任意の場所にポイントを置き、ヒットC-M-xして定義を再評価します。

標準のEmacsデバッガーを使用する

M-x debug-on-entry triangle-using-cond、その後、triangle-using-condが呼び出されると、Emacsデバッガー(バッファー*Backtrace*)に配置されます。

を使用して評価をステップ実行しますd(またはc、興味のない評価をスキップします)。

中間状態(変数値など)を表示するには、eいつでも使用できます。評価するためにsexpを入力するように求められ、評価結果が出力されます。

デバッガーを使用している間、ソースコードのコピーを別のフレームに表示しておくと、何が起こっているのかを追跡できます。

また、ソースコードの任意の場所で明示的な呼び出しを挿入して、デバッガー(多かれ少なかれブレークポイント)に入ることもできます。(debug)またはを挿入し(debug nil SOME-SEXP-TO-EVALUATE)ます。後者の場合、デバッガが入力SOME-SEXP-TO-EVALUATEされると評価され、結果が出力されます。(このようなコードをソースコードに挿入し、C-M-x評価に使用してから元に戻すことができます。編集したファイルを保存する必要はありません。)

詳細については、ElispマニュアルのノードUsing Debuggerを参照してください。

ループとしての再帰

とにかく、再帰をループと考えてください。定義されている2つの終了ケースがあります:(<= number 0)(= number 1)。これらの場合、関数は単純な数値を返します。

再帰的な場合、関数はその数との関数の結果の合計を返しますnumber - 1。最終的に、関数は1ゼロ以下のいずれかまたは数字で呼び出されます。

したがって、再帰的なケースの結果は次のとおりです。

(+ number (+ (1- number) (+ (1- (1- number)) ... 1)

例を挙げましょう(triangle-using-cond 4)。最終的な式を蓄積しましょう:

  • 最初の反復でnumber4であるため、(> number 1)分岐が続きます。私たちは、表現の構築を開始(+ 4 ...し、して関数を呼び出し(1- 4)、すなわち(triangle-using-cond 3)

  • 現在number3であり、結果は(+ 3 (triangle-using-cond 2))です。合計結果式は(+ 4 (+ 3 (triangle-using-cond 2)))です。

  • numberある2式があるので、今(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))

  • numberがあり1(= number 1)分岐を取り、退屈になり1ます。全体の表現は(+ 4 (+ 3 (+ 2 1)))です。:内部から出ていることを評価し、あなたが得る(+ 4 (+ 3 3))(+ 4 6)ちょうど、または10


3
Edebugはさらに改善されます。=)
マラバルバ14年

を使用して証跡を印刷する方法はmessage (...)、ヒットC-x C-eすると最終結果(10)が表示されるだけです?何か不足していますか?
博士号取得14年

@Malabarba、Edebug行動を起こす方法は?
博士号取得14年

1
@doctorateヒットC-u C-M-xは、関数内のポイントを使用して、それを編集します。次に、通常どおり関数を実行します。
マラバルバ14年

バッファに(message ...)出力するものを@doctorateし*Message*ます。
rekado

6

SICPの手続きアプリケーションの代替モデルは、このようなコードを理解するためのアルゴリズムを説明できます。

これを容易にするためのコードをいくつか書きました。 lispyパッケージlispy-flattenからこれを行います。に適用lispy-flattenした結果は(triangle-using-cond 4)次のとおりです。

(cond ((<= 4 0)
       0)
      ((= 4 1)
       1)
      ((> 4 1)
       (+ 4 (triangle-using-cond (1- 4)))))

上記の式を次のように単純化できます。

(+ 4 (triangle-using-cond 3))

その後、もう一度フラット化します。

(+ 4 (cond ((<= 3 0)
            0)
           ((= 3 1)
            1)
           ((> 3 1)
            (+ 3 (triangle-using-cond (1- 3))))))

最終結果:

(+ 4 (+ 3 (+ 2 1)))

3

これはEmacs / Elispに固有のものではありませんが、数学の背景がある場合、再帰は数学的帰納法に似ています。(またはそうしない場合:帰納法を学ぶとき、それは再帰のようなものです!)

定義から始めましょう:

(defun triangle-using-cond (number)
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

numberisの場合4、最初の2つの条件はどちらも成り立たないため、3番目の条件に従って
(triangle-using-cond 4)評価されます:はas
(+ number (triangle-using-cond (1- number)))、つまりasとして評価され
(+ 4 (triangle-using-cond 3))ます。

同様に、
(triangle-using-cond 3)はとして評価され
(+ 3 (triangle-using-cond 2))ます。

同様に、 (triangle-using-cond 2)はとして評価され
(+ 2 (triangle-using-cond 1))ます。

ただし、の(triangle-using-cond 1)場合、2番目の条件が成立し、と評価され1ます。

再帰を学んでいる人へのアドバイス:避けよう

再帰呼び出しが機能することを信頼する代わりに、再帰呼び出し中に何が起こるかを考えようとする一般的な初心者の間違い(再帰の飛躍と呼ばれることもあります)。

あなたが(triangle-using-cond 4)正しい答えを返すかどうかを自分で納得させようとしているなら、それが正しい答えを返すと仮定し(triangle-using-cond 3)、その場合に正しいかどうかを確かめてください。もちろん、ベースケースも確認する必要があります。


2

例の計算手順は次のようになります。

(4 +               ;; step 1
   (3 +            ;; step 2
      (2 +         ;; step 3
         (1))))    ;; step 4
=> 10

入力として1がすでに再帰を終了しているため、0条件は実際には満たされません。


(1)有効な式ではありません。
レカド14年

1
でうまく評価されM-x calcます。:-)しかし、真剣に、私はLisp評価ではなく計算を示すつもりでした。
パプリカ14年

ああ、私はそれがあなたの答えの(4 +代わりにあることにさえ気づきませんでした(+ 4... :)
rekado 14年

0

これは非常に簡単だと思います。この下でemacs lispを使う必要はありません。ただの小学校の数学です。

f(0)= 0

f(1)= 1

n> 1の場合、f(n)= f(n-1)+ n

したがって、f(5)= 5 + f(4)= 5 + 4 + f(3)= 5 + 4 + 3 + 2 + 1 + 0

今では明らかです。


ただし、この関数の場合、f(0)は呼び出されません。
rekado
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.