Một kỹ thuật khá hữu ích cho lập trình động được gọi là ghi nhớ. Để biết thêm chi tiết, xem ví dụ blog post by Don Syme hoặc introduction by Matthew Podwysocki.
Ý tưởng là bạn viết (ngây thơ) đệ quy chức năng và sau đó thêm bộ nhớ cache lưu trữ kết quả trước đó. Điều này cho phép bạn viết hàm theo kiểu chức năng thông thường, nhưng nhận được hiệu suất của thuật toán được triển khai bằng cách sử dụng lập trình động.
Ví dụ, một (không hiệu quả) chức năng ngây thơ để tính số Fibonacci trông như thế này:
let rec fibs n =
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2))
này là không hiệu quả, bởi vì khi bạn gọi fibs 3
, nó sẽ gọi fibs 1
ba lần (và nhiều lần hơn nếu bạn gọi, ví dụ: fibs 6
). Ý tưởng đằng sau việc ghi nhớ là chúng tôi viết bộ nhớ cache lưu trữ kết quả của fib 1
và fib 2
, v.v., do đó, các cuộc gọi lặp lại sẽ chỉ chọn giá trị được tính trước từ bộ nhớ cache.
Một chức năng chung mà không được memoization có thể được viết như thế này:
open System.Collections.Generic
let memoize(f) =
// Create (mutable) cache that is used for storing results of
// for function arguments that were already calculated.
let cache = new Dictionary<_, _>()
(fun x ->
// The returned function first performs a cache lookup
let succ, v = cache.TryGetValue(x)
if succ then v else
// If value was not found, calculate & cache it
let v = f(x)
cache.Add(x, v)
v)
Để viết chức năng Fibonacci hiệu quả hơn, bây giờ chúng ta có thể gọi memoize
và cung cấp cho nó chức năng mà thực hiện việc tính toán như một cuộc tranh cãi:
let rec fibs = memoize (fun n ->
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2)))
Lưu ý rằng đây là giá trị đệ quy - phần thân của hàm gọi hàm được ghi nhớ là fibs
.
Nó thường dễ dàng hơn nhiều để đọc một memoized (recursive) năng động vấn đề lập trình so với một chương trình được lập trình theo thủ tục, tuy nhiên có thể dễ dàng hơn để giải thích thời gian chạy của phiên bản thủ tục. – gradbot