随机惰性流:评估何时发生?
Lazy stream of random(s): When does evaluation happen?
我认为以下代码应该定义 1 到 10 之间的随机数流:
(define random-stream (stream-cons (random 1 11) random-stream))
然而,它实际上所做的是定义一个特定随机数的流。例如:
> (stream->list (stream-take random-stream 10))
'(5 5 5 5 5 5 5 5 5 5)
我认为这是 (random 1 11)
在首次解析定义时生成的随机数。我通过使 random-stream
成为无参数函数来解决这个问题:
(define (random-stream) (stream-cons (random 1 11) (random-stream)))
这个有效:
> (stream->list (stream-take (random-stream) 10))
'(6 1 10 9 4 2 2 3 3 10)
所以在我看来,可以理解的是,常量是在读取时求值的,而函数是在调用时求值的。通常这无关紧要,但在流的情况下——你有一个递归定义——这会有所不同。
这是它的工作原理,还是比这更微妙?关于这种差异,还有其他情况应该注意吗?
使 random-stream
成为无参数函数是正确的解决方案。
(define (random-stream) (stream-cons (random 1 11) (random-stream)))
我会解释为什么。
当您使用 (define my-stream (stream-cons ....))
正常定义流时,流只有一个值。对 my-stream
的任何引用都将产生相同的值。
(define my-stream (stream-cons (random 1 11) my-stream))
"rest" 中的 my-stream
与 my-stream
.
的值 eq?
完全相同
> (eq? my-stream (stream-rest my-stream))
#true
因为它们是相同的值,所以它们可以在函数调用中被替换。如果 (stream-first my-stream)
returns 5,则 (stream-first (stream-rest my-stream))
也必须 return 5。(这是因为 stream-first
是一个 "pure" 函数它return相同输入的相同输出。)
> (eq? (stream-first my-stream) (stream-first (stream-rest my-stream)))
#true
函数版本不是这种情况,因为每次调用函数时都会创建一个新的流值。
(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)
,其余与整体不同
> (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
我同意另一个答案,即 OP 代码的问题在于 random-stream
是一个流,其中 (stream-first random-stream)
是一些随机数,而 (stream-rest random-stream)
也是同一个流以相同的数字开头。
虽然我不太同意“无参数函数是正确的解决方案”。
另一种解决方案是使用 stream-map
将随机数映射到自然数上:
(define random-stream/1-10
(stream-map (lambda (x) (random 1 11)) (in-naturals)))
创建一个生成随机数流的函数甚至更好:
(define (random-stream a b)
(stream-map (lambda (x) (random a b)) (in-naturals)))
这个函数可以用来创建流(注意in-naturals
也是一个创建流的函数):
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
方法:
(define (random-stream-cons a b)
(stream-cons (random a b) (random-stream-cons a b)))
当在使用 random-stream-cons
创建的流上调用 stream-first
时,将 returned 一个随机数;当在同一个流上调用 stream-rest
时,另一个以随机数作为其第一个元素的流被 returned.
创建的流是持久的:
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
函数基本相同(但没有参数);然而它们都不是 streams。它们都是 create 流的函数:
(define (random-stream-cons/1-10) (stream-cons (random 1 11) (random-stream-cons/1-10)))
每次调用其中一个流创建函数时,都会return编辑一个新流:
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)
这可能正是我们所需要的;这些函数非常有用,例如,在迭代上下文中:
random_streams.rkt> (for ([x (stream-take (random-stream 1 11) 5)])
(displayln x))
2
8
9
1
3
因此,return 流的函数很有用,如果需要,可以将生成的流绑定到符号。对于可能需要多次使用不同值的流,可以在自定义流创建函数中提供参数。但是对于一次性流,stream-map
已经完成了 returning 一个流的工作,它可以像 OP 最初写的那样绑定到一个符号。
我认为以下代码应该定义 1 到 10 之间的随机数流:
(define random-stream (stream-cons (random 1 11) random-stream))
然而,它实际上所做的是定义一个特定随机数的流。例如:
> (stream->list (stream-take random-stream 10))
'(5 5 5 5 5 5 5 5 5 5)
我认为这是 (random 1 11)
在首次解析定义时生成的随机数。我通过使 random-stream
成为无参数函数来解决这个问题:
(define (random-stream) (stream-cons (random 1 11) (random-stream)))
这个有效:
> (stream->list (stream-take (random-stream) 10))
'(6 1 10 9 4 2 2 3 3 10)
所以在我看来,可以理解的是,常量是在读取时求值的,而函数是在调用时求值的。通常这无关紧要,但在流的情况下——你有一个递归定义——这会有所不同。
这是它的工作原理,还是比这更微妙?关于这种差异,还有其他情况应该注意吗?
使 random-stream
成为无参数函数是正确的解决方案。
(define (random-stream) (stream-cons (random 1 11) (random-stream)))
我会解释为什么。
当您使用 (define my-stream (stream-cons ....))
正常定义流时,流只有一个值。对 my-stream
的任何引用都将产生相同的值。
(define my-stream (stream-cons (random 1 11) my-stream))
"rest" 中的 my-stream
与 my-stream
.
eq?
完全相同
> (eq? my-stream (stream-rest my-stream))
#true
因为它们是相同的值,所以它们可以在函数调用中被替换。如果 (stream-first my-stream)
returns 5,则 (stream-first (stream-rest my-stream))
也必须 return 5。(这是因为 stream-first
是一个 "pure" 函数它return相同输入的相同输出。)
> (eq? (stream-first my-stream) (stream-first (stream-rest my-stream)))
#true
函数版本不是这种情况,因为每次调用函数时都会创建一个新的流值。
(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)
,其余与整体不同
> (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
我同意另一个答案,即 OP 代码的问题在于 random-stream
是一个流,其中 (stream-first random-stream)
是一些随机数,而 (stream-rest random-stream)
也是同一个流以相同的数字开头。
虽然我不太同意“无参数函数是正确的解决方案”。
另一种解决方案是使用 stream-map
将随机数映射到自然数上:
(define random-stream/1-10
(stream-map (lambda (x) (random 1 11)) (in-naturals)))
创建一个生成随机数流的函数甚至更好:
(define (random-stream a b)
(stream-map (lambda (x) (random a b)) (in-naturals)))
这个函数可以用来创建流(注意in-naturals
也是一个创建流的函数):
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
方法:
(define (random-stream-cons a b)
(stream-cons (random a b) (random-stream-cons a b)))
当在使用 random-stream-cons
创建的流上调用 stream-first
时,将 returned 一个随机数;当在同一个流上调用 stream-rest
时,另一个以随机数作为其第一个元素的流被 returned.
创建的流是持久的:
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
函数基本相同(但没有参数);然而它们都不是 streams。它们都是 create 流的函数:
(define (random-stream-cons/1-10) (stream-cons (random 1 11) (random-stream-cons/1-10)))
每次调用其中一个流创建函数时,都会return编辑一个新流:
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)
这可能正是我们所需要的;这些函数非常有用,例如,在迭代上下文中:
random_streams.rkt> (for ([x (stream-take (random-stream 1 11) 5)])
(displayln x))
2
8
9
1
3
因此,return 流的函数很有用,如果需要,可以将生成的流绑定到符号。对于可能需要多次使用不同值的流,可以在自定义流创建函数中提供参数。但是对于一次性流,stream-map
已经完成了 returning 一个流的工作,它可以像 OP 最初写的那样绑定到一个符号。