Bu özyineleme kodu nasıl anlaşılır?


12

Bu kodu , girilen satır sayısına göre çakıl sayısını bulmak için fonksiyon An Introduction to Programming in Emacs Lispyardımı ile özyineleme gösteren kılavuzda buldum cond, yani satırlar = 2 ise, çakıl taşları 3 olmalı, 4 satır ise 10 çakıl olmalıdır Orada.

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

4. argümanı geçtikten sonra 10 olarak değerlendirin:

(triangle-using-cond 4)

Kılavuz özellikle bu örnek koddaki her adımda ne olduğunu net bir şekilde açıklamamıştır ve burada özyinelemenin nasıl çalıştığını anlayamadım. Her bir durumda mekaniklerin adım adım ne olduğunu anlamama yardımcı olabilir misiniz ?


Başka birinin size "özyineleme" kelimesinde yardım etmesine izin vereceğim (çünkü bu bağlamda farklı bir şey olduğunu düşünüyorum) ya da yazmak üzere olduğumu daha iyi açıklayacağım: (a) sayı küçük veya eşitse 0, sonra 0; (b) sayı 1'e eşitse, o zaman 1; (c) sayı 1'den büyükse, işlevin döndürdüğü değere sayı eklenir triangle-using-condve bağımsız değişken sayı ne olursa olsun 1'den küçük olur. Koşullar a, b ve sonra c sırasına göre ilerler - önce eşleşen her şey, paranın durduğu yerdir.
hukukçu

her zamanki gibi elisp tercümanı en içten dışa doğru değerlendirir. Böylece 1-4 = 3,. Şimdi özyinelemeli çağrı olacak (triangle-using-cond 3), ancak bu 1 koşullu duruma gelinceye kadar aynı özyinelemeli çağrı ile sonuçlanacaktır, değil mi? sonrasında ne olacak?
doktora

Oh, anlıyorum - fonksiyon 3. adımda kendini tekrar kullanıyor - tamam, iyi bir nokta.
hukukçu

Acaba bunun sonucu ne olurdu (triangle-using-cond 3)?
doktora

2
nb İşlevi 1-özellikle yanıltıcı bir ad taşır; özellikle de bir çağrıyı infix gösterimi gibi okursanız. Eksi bir argümanını döndürür; DEĞİL bir eksi argüman.
phils

Yanıtlar:


14

"Printf hata ayıklama" özelliğini kullanma

İşlev tanımını değiştirerek Emacs'ın anlamanıza yardımcı olmasına izin verebilirsiniz:

(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))))))

Tampona (message ...)bir iz yazdırmak için bir yere ekleyin *Messages*.

Edebug Kullanımı

Noktayı fonksiyon tanımının içinde herhangi bir yere yerleştirin ve C-u C-M-x"enstrüman" a basın. Daha sonra, örneğin sonradan nokta yerleştirip (triangle-using-cond 3)vurarak işlevi değerlendirin C-x C-e.

Şimdi Edebug modundasınız. İşlev boyunca ilerlemek için boşluk çubuğuna basın. Her ifadenin ara değerleri yankı alanında gösterilir. Edebug modundan çıkmak için düğmesine basın q. Enstrümantasyonu kaldırmak için, tanım içinde herhangi bir noktaya gelin ve tanımı C-M-xyeniden değerlendirmek için tuşuna basın.

Standart Emacs hata ayıklayıcısını kullanma

M-x debug-on-entry triangle-using-cond, daha sonra triangle-using-condçağrıldığında, Emacs hata ayıklayıcısına (arabellek *Backtrace*) yerleştirilirsiniz.

Düğmelerini kullanarak değerlendirmede ilerleyin d(veya cilginç olmayan değerlendirmeler arasında geçiş yapın).

Ara durumu (değişken değerler vb.) Görmek için istediğiniz zaman kullanabilirsiniz e. Değerlendirmek için bir sexp girmeniz istenir ve değerlendirme sonucu yazdırılır.

Hata ayıklayıcıyı kullanırken, kaynak kodun bir kopyasını başka bir çerçevede görünür halde tutun, böylece neler olup bittiğini takip edebilirsiniz.

Ayrıca, kaynak kodundaki rasgele yerlerde hata ayıklayıcıya (az ya da çok kesme noktası) girmek için açık çağrılar ekleyebilirsiniz. Takabilir (debug)veya (debug nil SOME-SEXP-TO-EVALUATE). İkinci durumda, hata ayıklayıcı girildiğinde SOME-SEXP-TO-EVALUATEdeğerlendirilir ve sonuç yazdırılır. (Bu kodu kaynak koduna ekleyebileceğinizi ve C-M-xdeğerlendirmek için kullanabileceğinizi unutmayın , ardından geri alın - düzenlenen dosyayı kaydetmenize gerek yoktur.)

Daha Using Debuggerfazla bilgi için Elisp kılavuzuna, düğüme bakın.

Döngü olarak özyineleme

Her neyse, özyinelemeyi bir döngü olarak düşünün. Tanımlanmış iki fesih davası vardır: (<= number 0)ve (= number 1). Bu durumlarda işlev basit bir sayı döndürür.

Özyinelemeli durumda, işlev bu sayının toplamını ve ile işlevinin sonucunu döndürür number - 1. Sonunda, işlev ya 1sıfırdan küçük ya da ona eşit bir sayı ile çağrılır .

Özyinelemeli vaka sonucu bu nedenle:

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

Örneğin ele alalım (triangle-using-cond 4). Son ifadeyi biriktirelim:

  • birinci tekrarda numberise 4böylece, (> number 1)şube takip eder. Bir ifade oluşturmaya başlıyoruz (+ 4 ...ve işlevi (1- 4), yani ile çağırıyoruz (triangle-using-cond 3).

  • Şimdi numberise 3, ve sonuç (+ 3 (triangle-using-cond 2)). Toplam sonuç ifadesi (+ 4 (+ 3 (triangle-using-cond 2))).

  • numberolduğu 2ifade için bunu hemen(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))

  • numberolduğu 1şimdi ve biz almak (= number 1)bir sıkıcı sonuçlanan dalı 1. Bütün ifade (+ 4 (+ 3 (+ 2 1))). İçten dışa olduğunu değerlendirmek ve elde edersiniz: (+ 4 (+ 3 3)), (+ 4 6), ya da sadece 10.


3
Edebug daha da iyi olacak. =)
Malabarba

iz kullanarak yazdırmak için nasıl message (...), vurmak C-x C-esadece nihai sonucu gösterir (10) başka bir şey? Bir şey mi kaçırıyorum?
doktora

@Malabarba, nasıl Edebugeyleme geçirilir ?
doktora

1
@doctorate edebug C-u C-M-xişlevi içinde noktası ile vurmak . Sonra işlevi normal şekilde çalıştırın.
Malabarba

@doctorate (message ...)malzeme *Message*arabellek yazdırır .
rekado

6

SICP'den Prosedür Uygulaması için İkame Modeli böyle bir kodu anlamak için algoritmayı açıklayabilir.

Bunu da kolaylaştırmak için bazı kodlar yazdım. lispy-flattendan lispy paketin yapar. İşte uygulanması sonucunda elde var lispy-flatteniçin (triangle-using-cond 4):

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

Yukarıdaki ifadeyi basitleştirmek için:

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

Sonra bir kez daha düzleştirin:

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

Nihai sonuç:

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

3

Bu Emacs / Elisp'e özgü değildir, ancak matematik geçmişiniz varsa, özyineleme matematiksel tümevarım gibidir . (Ya da yapmazsanız: o zaman indüksiyon öğrendiğinizde, özyineleme gibidir!)

Tanım ile başlayalım:

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

Ne zaman numberbir 4üçüncü durumuna göre değerlendirilir yani iki koşulun tutmak ne ait:
(triangle-using-cond 4)olarak değerlendirilir
(+ number (triangle-using-cond (1- number))), yani gibi
(+ 4 (triangle-using-cond 3)).

Benzer şekilde,
(triangle-using-cond 3)olarak değerlendirilir
(+ 3 (triangle-using-cond 2)).

Benzer şekilde, (triangle-using-cond 2)olarak değerlendirilir
(+ 2 (triangle-using-cond 1)).

Ancak (triangle-using-cond 1), ikinci koşul geçerlidir ve olarak değerlendirilir 1.

Özyineleme öğrenen herkes için bir tavsiye: kaçınmaya çalışın

yinelemeli çağrının işe yaradığına (bazen özyinelemeli inanç sıçraması olarak da adlandırılır) güvenmek yerine özyinelemeli çağrı sırasında neler olduğunu düşünmeye çalışmanın yaygın başlangıç ​​hatası.

Eğer edip kendinizi ikna etmeye çalışıyorsanız (triangle-using-cond 4)doğru cevabı dönecektir, sadece varsayalım o (triangle-using-cond 3)doğru cevabı döndürür ve bu durumda doğru olacaktır olmadığını doğrulamak. Tabii ki temel durumu da doğrulamanız gerekiyor.


2

Örneğiniz için hesaplama adımları aşağıdaki gibi olacaktır:

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

0 koşulu aslında hiçbir zaman karşılanmaz çünkü bir girdi olarak 1 zaten özyinelemeyi sonlandırır.


(1)geçerli bir ifade değil.
rekado

1
İle gayet iyi değerlendirir M-x calc. :-) Cidden, Lisp değerlendirmesini değil hesaplamayı göstermek istedim.
kırmızı biber

Oh, cevabın (4 +yerine bunun olduğunu bile fark etmedim (+ 4... :)
rekado

0

Bence bu oldukça kolay, bunun altında emacs lisp'e ihtiyacınız yok, sadece ilkokul matematik.

f (0) = 0

f (1) = 1

n> 1 olduğunda f (n) = f (n-1) + n

yani f (5) = 5 + f (4) = 5 + 4 + f (3) = 5 + 4 + 3 + 2 + 1 + 0

Şimdi açık.


Ancak bu işlev durumunda f (0) asla çağrılmaz.
rekado
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.