【问题标题】:Memoise on recursive function递归函数的备忘录
【发布时间】:2015-03-18 21:54:40
【问题描述】:

简介

我有一个函数将日期作为输入,在一定时间内进行一些计算 - 由 Sys.sleep() 表示 - 删除日期中的所有 '-' 并返回一个字符:

library(maggritr)

auxialiaryCompute = function(vDate)
{
    Sys.sleep(1)
    vDate %>% as.character %>% gsub("-", "", .)
}

> auxialiaryCompute(as.Date("2015-01-14"))
[1] "20150114"

酷。上面的输出是'20150114'。现在我想在这个函数中包含以前的输出。或者前两天,或者.. n 之前的输出,直到过去的有限天称为loopBackMaxDate

粗递归

这是一种可能的递归代码:

compute = function(vDate, loopBackMaxDate=vDate, loopBackDays=0)
{
    d = as.Date # short alias

    dates = Filter(function(x) x>d(loopBackMaxDate), 
                   getPreviousDates(loopBackDays, d(vDate))) 

    if(length(dates)==0)
        return(auxialiaryCompute(vDate=vDate, previousOutputs=list()))

    previousOutputs = lapply(dates, function(u) compute(u, loopBackMaxDate, loopBackDays))

    auxialiaryCompute(vDate=vDate, previousOutputs=previousOutputs)
}

auxialiaryCompute = function(vDate, previousOutputs=list())
{
    Sys.sleep(1)
    vDate %>% as.character %>% gsub("-", "", .)
}

getPreviousDates = function(loopBackDays, vDate)
{
    if(loopBackDays==0) return()
    seq.Date(from=vDate-loopBackDays, to=vDate-1, by="days")
}

有了这个,我得到了和以前一样的结果(平均花费 1 秒):

> compute(as.Date("2015-01-14"))
[1] "20150114"

以下内容需要4 秒:

> system.time(compute("2014-05-05", loopBackMaxDate="2014-05-01", loopBackDays=1))
   user  system elapsed 
   0.00    0.00    3.99 

我要计算以下,需要 3 秒:

> system.time(compute("2014-05-04", loopBackMaxDate="2014-05-01", loopBackDays=1))
   user  system elapsed 
   0.02    0.00    3.01 

这很糟糕,因为我正在再次计算 vDate="2014-05-04"vDate="2014-05-03"vDate="2014-05-02" 的结果,而在调用 compute("2014-05-05", loopBackMaxDate="2014-05-01", loopBackDays=1) 时已经完成...

记忆递归

这是我使用 memoized 的方式:

library(memoise)

compute = memoise(function(vDate, loopBackMaxDate=vDate, loopBackDays=0)
{
    d = as.Date # short alias

    dates = Filter(function(x) x>d(loopBackMaxDate), getPreviousDates(loopBackDays, d(vDate))) 

    if(length(dates)==0)
        return(auxialiaryCompute(vDate=vDate, previousOutputs=list()))

    previousOutputs = lapply(dates, function(u) compute(u, loopBackMaxDate, loopBackDays))

    auxialiaryCompute(vDate=vDate, previousOutputs=previousOutputs)
})

auxialiaryCompute = memoise(function(vDate, previousOutputs=list())
{
    Sys.sleep(1)
    vDate %>% as.character %>% gsub("-", "", .)
})

首次运行(实际耗时 4 秒):

> system.time(compute("2014-05-05", loopBackMaxDate="2014-05-01", loopBackDays=1))
  user  system elapsed 
  0.00    0.00    4.01 

第二次运行需要 1 秒,而我预计需要 0 秒:

> system.time(compute("2014-05-04", loopBackMaxDate="2014-05-01", loopBackDays=1))
   user  system elapsed 
   0.00    0.00    0.99 

我认为我在某个地方完全错了......我可以将输出存储在一个全局变量中,但我真的想让它与记忆化或连续样式传递一起工作并避免冗余计算!

如果有人有想法,我将非常感激!

【问题讨论】:

  • TL;DR,但尽量避免在 R 中递归。另外,当你可以简单地使用 format 时,为什么还要使用 gsubas.character
  • @Roland:确实,即使不影响问题的真实内容,它也更简单。
  • @bsg : Sys.sleep 在那里,以便测量函数以预期的迭代次数进行迭代(您也可以为这个玩具示例添加一些 loginfo 和 sprintf)。这也是一种有效检查记忆效果的方法。
  • 非常感谢您的评论@Roland,您在日期问题上含蓄地指导了我!

标签: r recursion memoization memoise


【解决方案1】:

好的,首先,我在auxiliaryCompute 函数上添加了一些日志信息:

compute = memoise(function(vDate, loopBackMaxDate=vDate, loopBackDays=0)
{
    d = as.Date # short alias

    dates = Filter(function(x) x>d(loopBackMaxDate), getPreviousDates(loopBackDays, d(vDate))) 

    if(length(dates)==0)
    {
        loginfo("I reached the tail!")
        return(auxiliaryCompute(vDate=vDate, previousOutputs=0))
    }

    previousOutputs = lapply(dates, function(u){
                    compute(vDate=u, loopBackMaxDate=loopBackMaxDate, loopBackDays)
                  })

    auxiliaryCompute(vDate2=vDate, previousOutputs=previousOutputs)
})

auxiliaryCompute = memoise(function(vDate2, previousOutputs)
{
    loginfo("-------arguments in auxiliaryCompute are: vDate %s , previousOutputs %s", vDate2, unlist(previousOutputs))
#   Sys.sleep(1)
    vDate2 %>% as.character %>% gsub("-", "", .)
})

> compute("2015-01-10", "2015-01-01", 2)
2015-01-20 18:53:12 INFO::I reached the tail!
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-02 , previousOutputs 0
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-03 , previousOutputs 20150102
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-04 , previousOutputs 20150102,20150103
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-05 , previousOutputs 20150103,20150104
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-06 , previousOutputs 20150104,20150105
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-07 , previousOutputs 20150105,20150106
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-08 , previousOutputs 20150106,20150107
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-09 , previousOutputs 20150107,20150108
2015-01-20 18:53:12 INFO::-------arguments: vDate 2015-01-10 , previousOutputs 20150108,20150109
[1] "20150110"

> compute("2015-01-08", "2015-01-01", 2)
2015-01-20 18:54:11 INFO::-------arguments: vDate 2015-01-08 , previousOutputs 20150106,20150107
[1] "20150108"

第一个日志很好,我们每次每个日期只去一次(不是用 memoize 重复)。然而奇怪的是,在第二个日志中,函数auxiliaryCompute 使用参数vDate 2015-01-08 , previousOutputs 20150106,20150107 调用,因为它已经被执行(出现在第一个日志中)。

并且其他日期被正确记住了......只有第一个错误......这是因为它是一个 字符串 并且递归中的其他日期被强制为 Date 格式.

只在参数中加入日期就可以了:

> compute(as.Date("2015-01-08"), "2015-01-01", 2)
[1] "20150108"

这真的很狡猾,因为 R 不是一种强类型语言,主要是因为我“混淆”了 datesstrings

【讨论】:

    猜你喜欢
    • 2013-08-30
    • 1970-01-01
    • 2010-10-27
    • 2013-12-01
    • 2022-01-15
    • 2021-03-30
    • 2023-01-17
    • 1970-01-01
    • 2021-08-05
    相关资源
    最近更新 更多