R 递归函数回忆录

R 递归函数回忆录,r,recursion,memoization,memoise,R,Recursion,Memoization,Memoise,导言 我有一个函数,将日期作为输入,在一定时间内进行一些计算-由Sys.sleep表示-删除日期中的所有“-”,并返回一个字符: library(maggritr) auxialiaryCompute = function(vDate) { Sys.sleep(1) vDate %>% as.character %>% gsub("-", "", .) } > auxialiaryCompute(as.Date("2015-01-14")) [1] "201

导言

我有一个函数,将日期作为输入,在一定时间内进行一些计算-由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    4.01 
我想计算以下,它需要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的结果,而调用compute2014-05-05、loopBackMaxDate=2014-05-01、loopBackDays=1时已经完成了计算

记忆递归

以下是我如何完成备忘录:

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 
我想我在某个地方完全错了。。。我可以将输出存储在一个全局变量中,但我真的想让它与记忆或连续样式传递一起工作,并避免冗余计算


如果有人有主意,我将非常感激

好的,首先,我在辅助计算函数中添加了一些loginfo:

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"
第一个日志是好的,我们每次只去一次,每个日期不重复与备忘录。然而,奇怪的是,在第二个日志中,使用参数vDate 2015-01-08调用函数auxiliaryCompute,之前的输出2015010620150107,因为它已经在第一个日志中执行

其他日期都被正确地记住了。。。。只有第一个bug。。。这是因为它是一个字符串,递归中的其他日期被强制为日期格式

通过在参数中输入日期,它可以工作:

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

这真的很狡猾,因为R不是一个强类型语言,而且主要是因为我把日期和字符串搞混了,所以编码非常糟糕

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