R中的延迟序列

时间:2014-05-07 05:24:35

标签: r clojure lazy-sequences

在Clojure中,使用惰性序列构造函数创建无限序列很容易。例如,

(def N (iterate inc 0))

返回一个数据对象N,它等同于无限序列

(0 1 2 3 ...)

评估值N会导致无限循环。评估(take 20 N)会返回前20个数字。由于序列是惰性的,inc函数仅在您要求时才会迭代。由于Clojure是同质的,因此懒惰序列是递归存储的。

在R中,是否可以做类似的事情?您能否提供一些示例R代码,它产生的数据对象N等同于完​​全无限的自然数序列?评估完整对象N应该会产生一个循环,但像head(N)这样的东西应该只返回前导数字。

注意:我对懒惰序列而不是自然数本身更感兴趣。

编辑:以下是lazy-seq的Clojure来源:

(defmacro lazy-seq
"Takes a body of expressions that returns an ISeq or nil, and yields
a Seqable object that will invoke the body only the first time seq
is called, and will cache the result and return it on all subsequent
seq calls. See also - realized?"
{:added "1.0"}
[& body]
(list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))    

我正在寻找一个在R中具有相同功能的宏。

4 个答案:

答案 0 :(得分:11)

替代实施

简介

自从这篇文章以来,我有机会更频繁地在R工作,所以我提供了一个备用的基础R实现。同样,我怀疑你可以通过降低到C扩展级别来获得更好的性能。休息之后会有原始答案。

基地R的第一个挑战是缺少真正的cons(暴露在R级,即)。 R使用c进行混合cons / concat操作,但这不会创建链表,而是创建一个填充了两个参数元素的新向量。特别是,必须知道两个参数的长度,而不是惰性序列的情况。此外,连续的c运算表现出二次性能而不是恒定时间。现在,您可以使用" list" (它们是真正的向量,而不是链表),长度为2,可以模拟cons细胞,但是......

第二个挑战是强制数据结构中的承诺。 R使用隐式承诺有一些懒惰的评估语义,但这些是二等公民。返回显式承诺delay的函数已被弃用,有利于隐式delayedAssign,仅针对其副作用执行 - "未评估的承诺永远不可见。&#34 ;函数参数是隐式承诺,因此您可以接受它们,但是您可以在不强制它的情况下将承诺放入数据结构中。

CS 101

事实证明,通过回顾计算机科学101可以解决这两个挑战。数据结构可以通过闭包来实现。

cons <- function(h,t) function(x) if(x) h else t

first <- function(kons) kons(TRUE)
rest <- function(kons) kons(FALSE)  

现在由于R的懒惰函数参数语义,我们的缺点已经能够进行延迟序列。

fibonacci <- function(a,b) cons(a, fibonacci(b, a+b))
fibs <- fibonacci(1,1)

虽然我们需要一套惰性序列处理函数,但是为了有用。在Clojure中,作为核心语言一部分的序列处理函数也可以自然地与惰性序列一起工作。另一方面,R的序列函数不会立即兼容。许多人依赖提前知道(有限)序列长度。让我们定义一些能够处理延迟序列的人。

filterz <- function(pred, s) {
  if(is.null(s)) return(NULL)
  f <- first(s)
  r <- rest(s)
  if(pred(f)) cons(f, filterz(pred, r)) else filterz(pred, r) }

take_whilez <- function(pred, s) {
   if(is.null(s) || !pred(first(s))) return(NULL)
   cons(first(s), take_whilez(pred, rest(s))) }

reduce <- function(f, init, s) {
  r <- init
  while(!is.null(s)) {
    r <- f(r, first(s))
    s <- rest(s) }
  return(r) }

让我们用我们创造的东西来计算所有甚至斐波纳契数不到400万的数字(欧拉项目#2):

reduce(`+`, 0, filterz(function(x) x %% 2 == 0, take_whilez(function(x) x < 4e6, fibs)))
# [1] 4613732

原始答案

我对R非常生疏,但是因为(1)因为我熟悉Clojure,(2)我认为你没有把你的观点传达给R用户,我将根据illustration of how Clojure lazy-sequences work尝试草图。这仅用于示例目的,并未以任何方式调整性能。它可能更好地实现为C扩展(如果已经不存在)。

延迟序列的其余部分在thunk中生成计算。它没有被立即调用。当请求每个元素(或者元素块)时,调用下一个thunk来检索值。如果它继续,那个thunk可能会创建另一个thunk来表示序列的尾部。神奇之处在于:(1)这些特殊的thunk实现了一个序列接口,并且可以透明地使用它。(2)每个thunk只被调用一次 - 它的值被缓存 - 所以实现的部分是一个值序列。 / p>

标准示例首先

自然数

numbers <- function(x) as.LazySeq(c(x, numbers(x+1)))
nums <- numbers(1)

take(10,nums) 
#=> [1]  1  2  3  4  5  6  7  8  9 10

#Slow, but does not overflow the stack (level stack consumption)
sum(take(100000,nums))
#=> [1] 5000050000

Fibonacci序列

fibonacci <- function(a,b) { 
 as.LazySeq(c(a, fibonacci(b, a+b)))}

fibs <- fibonacci(1,1)

take(10, fibs)
#=> [1]  1  1  2  3  5  8 13 21 34 55

nth(fibs, 20)
#=> [1] 6765

接下来是天真的R实施

懒惰序列类

is.LazySeq <- function(x) inherits(x, "LazySeq")

as.LazySeq <- function(s) {
  cache <- NULL
  value <- function() {
    if (is.null(cache)) {
      cache <<- force(s)
      while (is.LazySeq(cache)) cache <<- cache()}
    cache}
  structure(value, class="LazySeq")}

一些使用LazySeq

实现的通用序列方法
first <- function(x) UseMethod("first", x)
rest <- function(x) UseMethod("rest", x)

first.default <- function(s) s[1]

rest.default <- function(s) s[-1]

first.LazySeq <- function(s) s()[[1]]

rest.LazySeq <- function(s) s()[[-1]]

nth <- function(s, n) {
  while (n > 1) {
    n <- n - 1
    s <- rest(s) }
  first(s) }

#Note: Clojure's take is also lazy, this one is "eager"
take <- function(n, s) {
  r <- NULL
  while (n > 0) {
    n <- n - 1
    r <- c(r, first(s))
    s <- rest(s) }
  r}

答案 1 :(得分:10)

iterators库可能能够实现您所寻找的目标:

library(iterators)
i <- icount()
nextElem(i)
# 1
nextElem(i)
# 2

您可以永远致电nextElem

答案 2 :(得分:5)

由于R在向量上效果最好,因此人们通常需要一个返回向量的迭代器,例如

library(iterators)
ichunk <- function(n, ..., chunkSize) {
    it <- icount(n)              # FIXME: scale by chunkSize
    chunk <- seq_len(chunkSize)
    structure(list(nextElem=function() {
        (nextElem(it) - 1L) * chunkSize + chunk
    }), class=c("abstractiter", "iter"))
}

典型用途是数百万范围内的chunkSize。至少这会加速这种方法的永久性。

答案 3 :(得分:0)

你没有陈述你的目标,所以我会用任何语言指出

j <- 1
while(TRUE) { x[j+1] <- x[j]+1  ; j<-j+1}

会给你一个无限的序列。你真的想用迭代器做什么?