如何使用Y-combinator进行缓存以实现此功能

我有一个硬币= [200; 100; 50; 20; 10; 5; 2; 1] list和这个递归函数来计算有多少种方法可以提供一定的更改(Spoiler alert for
Project Euler problem 31):

let rec f acc coins amount =
    if amount < 0 then 0L
    elif amount = 0 then acc
    else
        match coins with
        | [] -> 0L
        | c::cs ->
            f (acc + 1L) coins (amount - c) + f acc cs amount

除了大值的StackOverflowException之外,该函数需要很长时间.所以我记得Y combinator并且好奇如何将它应用于这个问题.有一点help和函数签名的两个小变化我到达了这个:

let f f acc coins amount =
    if amount < 0 then 0L
    elif amount = 0 then acc
    else
        match coins with
        | [] -> 0L
        | c::cs ->
            f (acc + 1L) coins (amount - c) + f acc cs amount

let rec Y f x = f (Y f) x

这工作,现在我想使用字典进行缓存.但是为此我不知道如何处理f的acc和coin参数.

在下面的代码中,字典已经有了疯狂的类型.在使用该函数后,它变为int – > int64,所以我希望我的字典有这两个类型参数,但事实并非如此.它编译并给出了正确的答案,但对于大型输入来说仍然非常慢 – 这种类型并不令人惊讶.

open System.Collections.Generic
let memoize (d:Dictionary<_, _>) f x =
    match d.TryGetValue(x) with
    | true, re -> re
    | _ ->
        let re = f x
        d.Add(x, re)
        re

let numberOfWaysToChange =
    let d = Dictionary<_,_>()
    fun x -> Y (f >> fun f x -> memoize d f x) 0L coins x

我尝试在所有地方坚持两个初始化参数0L和列表但我无法使任何其他变体工作.

如何在这个示例工作中进行缓存,i.即我如何修复参数,以便我的缓存类型为Dictionary< int,int64>?

PS:我知道我的f不是尾递归的,所以我可以用累加器参数来节省麻烦(还需要学习延续).

最佳答案 你几乎就在那里,你只需要将Y组合器的功能集成到递归memoization函数中.

let rec Y f x = f (Y f) x
// val Y : f:(('a -> 'b) -> 'a -> 'b) -> x:'a -> 'b

let memoize f =
    let d = new System.Collections.Generic.Dictionary<_,_>()
    let rec g x =
        match d.TryGetValue x with
        | true, res -> res
        | _ -> let res = f g x in d.Add(x, res); res
    g
// val memoize : f:(('a -> 'b) -> 'a -> 'b) -> ('a -> 'b) when 'a : equality

调整算法.

let cc f = function
| amount, _ when amount = 0 -> 1
| amount, _ when amount < 0 -> 0
| _, [] -> 0
| amount, hd::tl -> f (amount, tl) + f (amount - hd, hd::tl)

#time;;
Y cc (200, [200; 100; 50; 20; 10; 5; 2; 1])
memoize cc (200, [200; 100; 50; 20; 10; 5; 2; 1])
点赞