第再帰和は、その端部(n
)から出発[0..n]
値、このような:この方法で
1+(2+(3+(... + ((n-1) + n)) ...)))
、プログラムは最初の完全なシーケンスを生成する、すべての番号を列挙しており、後にのみその加算は実際に実行することができる。
これには、O(n)メモリとO(n)時間が必要です。第二の再帰で
我々が以前に行ったように、我々は0
からn
に数えるが、私たちが行くように、我々は3
にカウントアップする前に
(((1+2)+3)+4)+ ...
に我々は1+2
合計することができますように、今、私たちは、数字を合計します。その後、前の合計の結果をそのまま残すことができます。1+2
、1
と2
をメモリから破棄します。したがって、プロセス全体において、我々は1)今までに遭遇した数の合計の結果と、2)シーケンスの次の数だけを記憶しておく。
したがって、O(1)メモリとO(n)時間だけが必要です。
注:Haskellは遅延型であるため、上記の議論は、部分集計がすべての再帰で実際に強制される場合にのみ保持されます。この強制は、コンパイラオプティマイザによって静かに追加されるかもしれませんが、明示的にすることをお勧めします。 in
sumdown n = sumd n 0
sumd 0 !a = a
sumd n !a = sumd (n-1) (n+a)
-- here I am using the BangPatterns extension,
-- otherwise, seq can be used instead
第2回目の再帰は、通常「アキュムレータスタイル」と呼ばれ、「末尾再帰」の特定のケースです。
(注2:末尾再帰は常にHaskellのような怠惰な言語では良いアイデアではありませんが、周りに渡されたデータは、シンプルであれば、例えば番号のようではなく、リストが好き、末尾再帰は通常有益である。)
出典
2017-05-26 11:07:25
chi
'a'は実際はアキュムレータであり、再帰をテールコールに最適化させるのに役立ちます。これは、最初の例とは異なり、コールスタックを拡張しないことを意味します。 – Redu
@Redu Haskellにはコールスタックはありません。サンクスタックを持っています。偶然、怠け者は、入力全体を検査するまで関数が戻ってこないようにするため、アキュムレータを使用すると、怠け者に干渉するので、迷子のようなものを盲目的に再帰的な形にすることは実際にはHaskellプログラムのパフォーマンスを損なう可能性があります。 – Ben