rgrinberg が提供するソリューションは一般化できるため、任意の関数をメモ化できます。ハッシュテーブルの代わりに連想リストを使用します。しかし、それは実際には問題ではありません。すべての例をハッシュテーブルを使用するように簡単に変換できます。
まず、memo
別の関数を受け取り、メモ化されたバージョンを返す関数を次に示します。これは、nlucaroni がコメントの 1 つで提案したものです。
let memo f =
let m = ref [] in
fun x ->
try
List.assoc x !m
with
Not_found ->
let y = f x in
m := (x, y) :: !m ;
y
この関数は、これまでに計算された結果memo f
のリストを保持します。m
計算を求められるf x
と、まずが既に計算されているかどうかを確認m
します。f x
はいの場合は結果を返します。そうでない場合は、実際に を計算f x
し、結果を に格納してm
返します。
が再帰的memo
である場合、上記には問題があります。f
一度computeをmemo
呼び出すと、 によって行われた再帰呼び出しは によってインターセプトされません。この問題を解決するには、次の 2 つのことを行う必要があります。f
f x
f
memo
このような再帰の定義では、f
再帰呼び出しを「後で提供される」関数の呼び出しに置き換える必要があります (これは のメモ化されたバージョンになりますf
)。
では、約束された「再帰呼び出しを行いたいときに呼び出す関数」memo f
を提供する必要があります。f
これにより、次の解決策が導き出されます。
let memo_rec f =
let m = ref [] in
let rec g x =
try
List.assoc x !m
with
Not_found ->
let y = f g x in
m := (x, y) :: !m ;
y
in
g
これがどのように機能するかを示すために、単純なフィボナッチ関数をメモしてみましょう。追加の引数を受け入れるように記述する必要があります。これを と呼びますself
。この引数は、関数が自分自身を再帰的に呼び出す代わりに使用するものです。
let fib self = function
0 -> 1
| 1 -> 1
| n -> self (n - 1) + self (n - 2)
メモ化された を取得するために、fib
計算します
let fib_memoized = memo_rec fib
fib_memoized 50
すぐに元に戻るので、ぜひお試しください。(これは、通常の素朴な再帰的定義memo f
である where には当てはまりません。)f