[関数] 制御フロー、再帰、高階関数を理解する記事です。

目次

制御フロー

条件文

反復ステートメント

例: 素因数分解

 

再帰

例: 階乗

例: フィボナッチ数列

例:奇数と偶数の判定

高次関数

ラムダ式

デザイン機能

例:累積計算

例: カリー化

ラボ 1: 機能、制御

宿題 2: 高階関数

プロジェクト 1: 豚のゲーム


制御フロー

特定の操作を実行するためにインタープリターによって実行されるステートメント。

たとえば、複合ステートメント全体 (複合ステートメント) は def Python で宣言され、ヘッダーによって header 単純なステートメント (句) のタイプが決定され、その後にステートメント シーケンス (スイート) が続きます。インタプリタは、この一連のステートメントを特定の順序で実行します。

条件文

条件文は、ほとんどの言語でキーワードとして表示されます if 。

Python では True 、 と は False それぞれ true または false を表します。 if は条件文とその true 分岐を導き、0 個または 1 つの else は false 分岐を導き、ネストする elif は 0 個以上存在する場合があります。

def absolute_value(n):
    if n < 0:
        return -n
    elif n == 0:
        return 0
    else:
        return n

 スキームでは #t 、 と は #f それぞれ true または false を表し、文法的に elif をネストすることはできません (if test consequent alternative)

(define (absolute-value n)
  (if (positive? n) n (- n)))

FP は通常、ガードのような構文のセットを提供します。つまり、条件ステートメントは任意の数の条件判断を受け入れることができ、条件判断は上から下に実行されます。条件が true の場合、ステートメント ブロックが実行され、条件文が実行されます。ステートメントが終了します。Conforms にはラップアラウンド用のデフォルトのブロックがあります。実際、このステートメントは の if-then-elif-else 変形に似ています。

(cond ((< n 0) (- n))
      ((= n 0) n)
      (else n))

アーランでは、if は cond です。

absolute_value(N) ->
    if
        N < 0 -> -N;
        N =:= 0 -> N;
        true -> N
    end.

反復ステートメント

反復ステートメントはループ ステートメントとも呼ばれ、条件が満たされるとループ本体が実行され、条件が満たされなくなるまで終了します。

i = 0
total = 0
while i < 3:
    total = total + i
    i = i + 1
    print(total)

print(total)
# 0
# 1
# 3

このスキームのループは、通常の言語の while とは少し異なりますが、言い方はどうあれ、C 言語の for に似ています (あるいは、C の for は Lisp の do に近いかもしれません)。 、変数変更ループ文です。

(do ((i 0 (+ i 1)) (total 0 (+ total i))) ;; assignment
    ((> i 2) total) ;; exit
  (display total) ;; body
  (newline))
;; 0
;; 0
;; 1
;; 3 -- not print

 純粋な FP として、erlang には使用可能な while ステートメントがなく、末尾再帰を使用してシミュレートする必要があります。

loop(3, Total) -> Total;
loop(I, Total) -> loop(I + 1, Total + I).

 

例: 素因数分解

素因数分解は、ループと条件を使用して答えを見つける必要がある典型的な問題です。正の整数 N ごとに、そのすべての素因数のセットを分解できます。

  • 8 = 2 * 2 * 2
  • 9 = 3 * 3
  • 10 = 2 * 5
  • 11 = 11

より良い方法は、この正の整数の最小の素因数を見つけて、分解が完了するまで残りの部分の分解を続けることです。

858 = 2∗429 = 2∗3∗143 = 2∗3∗11∗13

(define (get-primes n)
  (let ((bits (make-vector (+ n 1) #t)))
    (let loop ((p 2) (ps '()))
      (cond ((< n p) (reverse ps))
            ((vector-ref bits p)
             (do ((i (+ p p) (+ i p))) ((< n i))
               (vector-set! bits i #f))
             (loop (+ p 1) (cons p ps)))
            (else (loop (+ p 1) ps))))))

(define (get-factorization n primes)
  (let ((prime (car primes))
        (others (cdr primes)))
    (if (= (remainder n prime) 0)
        prime
        (get-factorization n others))))

(define (prime-factorization n)
  (if (< n 3)
      (list n)
      (let ((primes (get-primes n)))
        (let loop ((num n) (ans '()))
          (cond ((= num 1) (reverse ans))
                (else (let ((prime (get-factorization num primes)))
                        (loop (quotient num prime) (cons prime ans)))))))))

 

再帰

再帰 数学およびコンピュータ サイエンスにおいて、定義に関数自体を使用する方法。再帰という用語は、自己相似的な方法で物事を繰り返すプロセスを説明するためによく使用されます。たとえば、2 つのミラーが互いにほぼ平行である場合、ミラー内にネストされたイメージが無限再帰的に表示されます。それは自己複製のプロセスとしても理解できます。

再帰プロセスを理解するのに役立つかもしれない説明を次に示します。

  1. まだ終わってないか?完了した場合は結果を返します。このような終了条件がなければ、再帰は永遠に続きます。
  2. そうでない場合は、問題を単純化し、より簡単な問題を解決し、その結果を元の問題の解決策にまとめます。その後、その解決策に戻ります。

例: 階乗

階乗(0)=1、

階乗(1)=1、

階乗(N)=N×階乗(N−1)

(define (factorial n)
  (cond ((= n 0) 1)
        (else (* n (factorial (- n 1))))))

 この計算プロセスでは、置換モデルを通じて、計算が徐々に拡大し、その後縮小する形状であることがわかります。計算プロセスは形成されたチェーンを構築し、縮小段階はこれらの操作を実際に実行します推迟进行的操作。というプロセスが呼び出されます递归计算过程このプロセスが実行される場合、インタプリタは将来実行される演算の軌跡を維持する必要があります。この例では、遅延される乗算チェーンの長さは、その乗算チェーンを保存するために保存する必要がある情報の量です。この長さは n の値とともに増加し、線形に成長します。このプロセスは線形再帰計算と呼ばれます。

(factorial 5)
(* 5 (factorial 4))
(* 5 (* 4 (factorial 3)))
(* 5 (* 4 (* 3 (factorial 2))))
(* 5 (* 4 (* 3 (* 2 (factorial 1)))))
(* 5 (* 4 (* 3 (* 2 (* 1 (factorial 0))))))
(* 5 (* 4 (* 3 (* 2 (* 1 1)))))
(* 5 (* 4 (* 3 (* 2 1))))
(* 5 (* 4 (* 3 2)))
(* 5 (* 4 6))
(* 5 24)
120

 別の実装を見てみましょう

(define (factorial n)
  (let factorial-iter ((product 1) (counter 1))
    (if (> counter n)
        product
        (factorial-iter (* counter product)
                        (1+ counter)))))

 この計算プロセスには成長も縮小もありません。計算プロセスの各ステップで保存する必要がある軌跡は、変数 product sum の現在値です。このプロセスを反復計算プロセスcounter と呼びます反復計算プロセスは、固定数の状態変数によって状態を記述できる計算プロセスであると同時に、計算​​プロセスがある状態から別の状態に遷移するときに状態変数がどのように更新されるかを記述する一連の固定ルールがあります。終了状態の検出は、計算プロセスがどのように終了するかを説明するために使用されます。

(factorial-iter   1 1 5)
(factorial-iter   1 2 5)
(factorial-iter   2 3 5)
(factorial-iter   6 4 5)
(factorial-iter  24 5 5)
(factorial-iter 120 6 5)
120

 

例: フィボナッチ数列

フィボナッチ(0)=0、

フィボナッチ(1)=1、

フィボナッチ(N)=フィボナッチ(N−1)+フィボナッチ(N−2)

フィボナッチ数列は自然な再帰関数であり、関数の再帰は前のコードで発生していることがわかります。コードの実装を見てください。

(define (fibonacci n)
  (cond ((= n 0) 0)
        ((= n 1) 1)
        (else (+ (fibonacci (- n 1))
                 (fibonacci (- n 2))))))

フィボナッチ関数のコールグラフを描くとツリーのように見えることがわかり、このような再帰を ツリー再帰と呼びます。しかし、計算の繰り返しが多く、意味のない計算が行われ、CPU パフォーマンスが無駄になります。

 

このフィボナッチ数列の計算方法は非常に悪く、多くの冗長な計算が行われ、再帰回数は n のサイズに応じて指数関数的に増加するため、反復法を使用して解のプロセスを最適化する必要があります。

(define (fibonacci n)
  (let fibonacci-iter ((a 1) (b 0) (counter 1))
    (if (> counter n)
        b
        (fibonacci-iter (+ a b) a (+ counter 1)))))

 ツリー再帰計算手順は役に立たないわけではありません。対数演算ではなく階層構造のデータに対する操作を考える場合、ツリー再帰計算手順はプログラムを理解し、設計するための自然かつ強力なツールです。

例:奇数と偶数の判定

モジュロの方法を直接使用して奇数か偶数かを判断できない場合は、前の数値が奇数か偶数かを尋ねるという単純かつ明確な方法があります。明らかに、これは再帰的な質問であり、答えが得られるまで質問を続ける必要があります。

(define (odd? n)
  (if (= n 0)
      #f
      (even? (- n 1))))
(define (even? n)
  (if (= n 0)
      #t
      (odd? (- n 1))))

 複数の関数が相互に再帰的に呼び出すこの方法は、間接再帰と呼ばれます。

高次関数

高階関数 (Higher-Order Functions) は、関数をパラメーターとして渡し、戻り値として渡す関数です。この機能は主に FP 機能を備えた言語で発生し、多くの場合、これらの言語は同時にラムダも提供します。

ラムダ式

 ラムダ式は、 現在の環境内のいくつかの変数をキャプチャする関数を定義する簡素化された方法であり、クロージャとも呼ばれます。Lambda は高階関数とともに使用されることが多く、通常は条件付き述語を渡すときに、関数パスを作成するのではなく、対応する Lambda オブジェクトを作成します。

(lambda (x)
  (= x 0))

ラムダは通常の関数と何ら変わりはなく、 ヘッダー (ラムダ キーワード)、パラメータ リスト関数本体の 3 つの部分で構成されます。また、ラムダを再帰的に使用することはできないため、ラムダを再帰的に使用したい場合は、Y コンビネータを使用する必要があります。1、2  _  _

lambdaで取得した環境変数は直接使用可能

(define (zero? x)
  ((lambda () (= x 0))))
(zero? 0) ;; #t
(zero? 1) ;; #f

デザイン機能

関数の設計時には、次の 3 つの側面に注意する必要があります。

  1. 各関数は正確なタスクを 1 つだけ持つ必要があり、複数のタスクを実行する関数は複数の関数に分割する必要があります。
  2. 同じことを繰り返さないでください (DRY、同じことを繰り返さないでください)。コードの一部をコピーアンドペーストしていることに気付いた場合は、おそらく関数を使用してそれを抽象化する機会を見つけたことがあるでしょう。
  3. この関数は、 square と cubeを提供せずにpow を提供し 、それぞれ square と cube を実現するべき乗を指定するなど、より一般的なように設計する必要があります。 

例:累積計算

たとえば、 設計関数の 3 つの側面に従って、 などを含む累積を計算する必要があります。累積用の関数を設計する必要があります\sum_{i=1}^{n}{i}\sum_{i=1}^{n}{i^{2}}さらに、この関数は一般性を提供できるほど抽象的である必要があります。

次に、2 つのパラメータを定義できstartend累積関数の上限と下限を識別するために使用されます。最も重要なことは、累積方法とこの関数をどのように伝えるかです。この関数を高次関数として設計してください。

 

(define (summation start end term)
  (let summation-iter ((counter start) (value 0))
    (if (> counter end)
        value
        (summation-iter (+ counter 1) (+ value (term counter))))))
(summation 0 10 (lambda (x) x))  ;; sum (i), 55
(summation 0 10 (lambda (x) (* x x))) ;; sum (i^2), 385
(summation 0 10 (lambda (x) (sqrt x))) ;; sum (sqrt(i)), 22.4682

例: カリー化

カリー化(カリー化)とは数学やFPの重要な機能で、複数のパラメータを受け取る関数を1つのパラメータを受け取る関数に変換し、残りのパラメータを受け取って結果を返す新しい関数を返す手法です。したがって、これら 3 つの式は同等です。

 

(define (sum a b) (+ a b))
(define (sum-curry a) (lambda (b) (+ a b)))
(define add10 (sum-curry 10))
(add10 5)  ;; 15
(sum 10 5) ;; 15

ラボ 1: 機能、制御

(define (falling n k)
  "Compute the falling factorial of n to depth k."
  (define factorial
    (lambda (n)
      (cond ((= n 0) 1)
            ((= n 1) 1)
            (else (* n (factorial (- n 1)))))))
  (/ (factorial n)
     (factorial (- n k))))

 

(define (sum-digits n)
  "Sum all the digits of y."
  (let sum-digits-iter ((num n) (val 0))
    (if (= num 0)
        val
        (sum-digits-iter (quotient num 10) (+ val (remainder num 10))))))

(define (double-eights n)
  "Return true if n has two eights in a row."
  (let double-eights-iter ((num n) (prev #f))
    (if (= num 0)
        #f
        (let ((curr (= (remainder num 10) 8)))
          (or (and curr prev)
              (double-eights-iter (quotient num 10) curr))))))

宿題 2: 高階関数

製品

         計算する項(1) \times 項(2) \times \cdots \times 項(n)

(define (product n term)
  "Return the product of the first n terms in a sequence."
  (let product-iter ((counter 1) (init 1))
    (if (> counter n)
        init
        (product-iter (+ counter 1) (* init (term counter))))))

 

蓄積する

         蓄積機能        

(define (accumulate merger init n term)
  "Return the result of merging the first n terms in a sequence and start.
    The terms to be merged are term(1), term(2), ..., term(n). merger is a
    two-argument commutative function."
  (let accumulate-iter ((counter 1) (value init))
    (if (> counter n)
        value
        (accumulate-iter (+ counter 1) (merger value (term counter))))))

 

プロジェクト 1: 豚のゲーム

知っている!高次関数を使用して、より高いロールを注文します。

Hog では、2 人のプレーヤーが交代でゴールに近づこうとし、少なくともゴールを合計した最初のプレーヤーが勝ちとなり、デフォルトのゴールは 100 ポイントです。各試行において、プレーヤーは振るために最大 10 個のサイコロを選択します。プレーヤーのスコアは、現在のラウンドのすべてのサイコロの出目の合計です。サイコロを振りすぎると、プレイヤーは一定のリスクを負います。

  • Sow Sad、サイコロの結果が 1 の場合、現在のプレイヤーのターン スコアは 1 になります。

通常のホッグ ゲームでは、これで十分です。いくつかのゲーム機能を追加するために、いくつかの特別なルールを追加します。

  • Pig Tail では、0 個のサイコロを振ることを選択したプレイヤーが2 \times \lvert{10 - 1}\rvert + 1 ポイントを獲得。ここでtens、 と はones相手のスコアの十の位と一の位を指します。

  • Square Swine は、プレイヤーが自分のターンでポイントを獲得し、最終結果が完全な正方形である場合、スコアを次の完全な正方形に設定します。

おすすめ

転載: blog.csdn.net/qq_62464995/article/details/128474790