首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >懒惰的随机流:评估什么时候发生?

懒惰的随机流:评估什么时候发生?
EN

Stack Overflow用户
提问于 2020-04-06 10:18:53
回答 2查看 112关注 0票数 3

我认为,下面的代码应该定义1到10之间的随机数流:

代码语言:javascript
复制
(define random-stream (stream-cons (random 1 11) random-stream))

然而,它实际上所做的是定义一个特定随机数的流。例如:

代码语言:javascript
复制
> (stream->list (stream-take random-stream 10))
'(5 5 5 5 5 5 5 5 5 5)

我猜想这是(random 1 11)第一次解析定义时产生的随机数。我通过使random-stream成为一个无论据的函数来解决这个问题:

代码语言:javascript
复制
(define (random-stream) (stream-cons (random 1 11) (random-stream)))

这样做是可行的:

代码语言:javascript
复制
> (stream->list (stream-take (random-stream) 10))
'(6 1 10 9 4 2 2 3 3 10)

因此,在我看来,常量是在读取时计算的,而函数则是在调用时计算的。通常情况下,这并不重要,但是在流的情况下--你有一个递归的定义--这会产生不同的结果。

这是它的工作方式,还是它比这个更微妙?对于这种差异,是否还有其他的情况值得注意呢?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-04-06 13:55:30

使random-stream成为无参数函数是正确的解决方案。

代码语言:javascript
复制
(define (random-stream) (stream-cons (random 1 11) (random-stream)))

我会解释原因。

通常使用(define my-stream (stream-cons ....))定义流时,流只有一个值。任何对my-stream的引用都会产生相同的值。

代码语言:javascript
复制
(define my-stream (stream-cons (random 1 11) my-stream))

"rest“中的my-stream实际上是与一个my-stream相同的值。

代码语言:javascript
复制
> (eq? my-stream (stream-rest my-stream))
#true

因此,因为它们是相同的值,所以可以在函数调用中替换它们。如果(stream-first my-stream)返回5,那么(stream-first (stream-rest my-stream))也必须返回5 (这是因为stream-first是一个“纯”函数,因为它对相同的输入返回相同的输出。)

代码语言:javascript
复制
> (eq? (stream-first my-stream) (stream-first (stream-rest my-stream)))
#true

函数版本的情况并非如此,因为每次调用函数时,它都会创建一个新的流值。

代码语言:javascript
复制
(define (random-stream) (stream-cons (random 1 11) (random-stream)))

> (eq? (random-stream) (random-stream))
#false
> (eq? (stream-first (random-stream)) (stream-first (random-stream)))
#false

因为" rest“字段也调用了(random-stream),所以其余部分与整体不同。

代码语言:javascript
复制
> (define generated-stream (random-stream))
> (eq? generated-stream (stream-rest generated-stream))
#false
> (eq? (stream-first generated-stream) (stream-first (stream-rest generated-stream)))
#false
票数 1
EN

Stack Overflow用户

发布于 2020-04-06 22:03:21

我同意另一个答案,即OP代码的问题是,random-stream是一个流,对于这个流,(stream-first random-stream)是一些随机数,而(stream-rest random-stream)也是以相同数目开头的相同流。

不过,我不太同意“没有论证的函数才是正确的解决办法”。

另一种解决方案是使用stream-map将随机数映射到自然数之上:

代码语言:javascript
复制
(define random-stream/1-10
  (stream-map (lambda (x) (random 1 11)) (in-naturals)))

更好的做法是创建一个函数,生成一个随机数流:

代码语言:javascript
复制
(define (random-stream a b)
  (stream-map (lambda (x) (random a b)) (in-naturals)))

此函数可用于创建流(请注意,in-naturals也是创建流的函数):

代码语言:javascript
复制
random_streams.rkt> (define my-stream (random-stream 1 11))
random_streams.rkt> (stream->list (stream-take my-stream 10))
'(1 1 2 7 5 7 4 2 2 9)

使用创建流的函数的概念,可以拯救stream-cons方法:

代码语言:javascript
复制
(define (random-stream-cons a b)
  (stream-cons (random a b) (random-stream-cons a b)))

当在用stream-first创建的流上调用random-stream-cons时,返回一个随机数;当在同一流上调用stream-rest时,返回另一个具有随机数的流作为其第一个元素。

所创建的流是持久的:

代码语言:javascript
复制
random_streams.rkt> (stream->list (stream-take random-stream/1-10 10))
'(10 9 9 1 2 7 6 2 6 6)
random_streams.rkt> (stream->list (stream-take random-stream/1-10 15))
'(10 9 9 1 2 7 6 2 6 6 10 1 2 8 5)

random_streams.rkt> (define my-stream-1 (random-stream 1 11))
random_streams.rkt> (stream->list (stream-take my-stream-1 10))
'(1 4 1 10 7 9 9 9 2 9)
random_streams.rkt> (stream->list (stream-take my-stream-1 15))
'(1 4 1 10 7 9 9 9 2 9 2 3 9 9 10)

random_streams.rkt> (define my-stream-2 (random-stream-cons 1 11))
random_streams.rkt> (stream->list (stream-take my-stream-2 10))
'(10 4 6 1 4 2 10 5 3 6)
random_streams.rkt> (stream->list (stream-take my-stream-2 15))
'(10 4 6 1 4 2 10 5 3 6 1 5 7 5 5)

这个random-stream-cons/1-10函数与早期的random-stream-cons函数本质上是相同的(但没有参数);但是它们都不是流。它们都是创建流的函数:

代码语言:javascript
复制
(define (random-stream-cons/1-10) (stream-cons (random 1 11) (random-stream-cons/1-10)))

每次调用其中一个流创建函数时,都会返回一个新流:

代码语言:javascript
复制
random_streams.rkt> (stream->list (stream-take (random-stream-cons/1-10) 10))
'(10 8 3 10 8 8 1 8 4 5)
random_streams.rkt> (stream->list (stream-take (random-stream-cons/1-10) 10))
'(1 8 7 3 8 2 2 10 6 5)

这可能正是我们所希望的;例如,在迭代环境中,这些函数非常有用:

代码语言:javascript
复制
random_streams.rkt> (for ([x (stream-take (random-stream 1 11) 5)])
                      (displayln x))
2
8
9
1
3

因此,返回流的函数是有用的,如果需要,结果流可以绑定到一个符号。对于可能需要多次使用不同值的流,可以在自定义流创建函数中提供参数。但是对于一次性流,stream-map已经完成了返回可以绑定到符号的流的工作,就像OP最初编写的那样。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61057542

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档