如何在Scheme(Racket或ChezScheme)中实现Python风格的生成器?

时间:2014-08-12 16:19:13

标签: scheme

今天我使用Scheme解决了N-queen问题,但与相同版本的Python相比,它非常慢。当N = 8时,Scheme需要90秒以上!我知道一个原因是我不能在Scheme中使用生成器,我的代码必须首先形成大型列表,这是内存和计算的噩梦。

在Scheme中关于生成器的主题很少,this one是我发现的唯一可能有用但可悲的是它在球拍或chez方案中都不起作用。

实际上,我只想要一个简单版本的python生成器,也就是说,不要形成整个列表,只需一次向我发送一个值。即:

(range 100000) ; will consume a large memory

(define g (generator 100000)) ; will do nothing
(next g) ;0 <-you call it with next one time, it returns one value
(next g) ;1
;...
(next g) ;100000
(next g) ;return a value that indicates the end, such as #f.

如果这很难,任何相关链接或类似的实现主题也会受到赞赏。我真的厌倦了搜索。谢谢!

如果需要,这是我的N-queen Scheme代码:

(define (range n)
    (define (recur n)
        (if (= n -1)
            '()
            (cons n (recur (- n 1)))))
    (recur (- n 1)))

(define (flatten a)
    (if (null? a)
        '()
        (append (car a) (flatten (cdr a)))))

(define (safe? x y sln)
    (if (null? sln)
        #t
        (let ((px (car (car sln))) (py (cadr (car sln))))
            (if (or (= y py) (= (- py y) (- px x)) (= (- py y) (- x px)))
                #f 
                (safe? x y (cdr sln))))))

(define (nqueen n)
    (define (recur x)
        (if (= x -1)
            (list '())
            (flatten (map (lambda (y) (map (lambda (sln) (cons (list x y) sln)) (filter (lambda (sln) (safe? x y sln)) (recur (- x 1))))) (range n)))))
    (recur (- n 1)))

(define (pl a)
    (if (null? a)
        '()
        (begin (display (car a)) (display "\n") (pl (cdr a)))))

(pl (nqueen 4))

4 个答案:

答案 0 :(得分:4)

对于这种情况使用延续(如链接中所示)是不合理的。这里有一个更简单的想法:让我们将我们的生成器定义为thunk(一个无参数函数),它将环境的一部分,最大允许值,增量和当前元素存储为其环境的一部分。每次调用该过程时,都会更新当前元素。以下代码的行为类似于Python 3.x range()函数(或Python 2.x xrange()):

(define (generator start stop step)
  (let ((current (- start 1)))
    (lambda ()
      (cond ((>= current stop) #f)
            (else
             (set! current (+ current step))
             current)))))

现在next过程只会调用生成器,直到达到最大值,此时生成器将开始为每个后续调用返回#f

(define (next generator)
  (generator))

例如:

(define g (generator 0 3 1))
(next g) ; 0
(next g) ; 1
(next g) ; 2
(next g) ; 3
(next g) ; #f

另一种选择是使用流,但我会坚持使用上面的解决方案,它很简单,应该适用于任何Scheme解释器。还有另一种选择 - 如果您在Racket中编码,只需使用sequence(也是一个流),如下所示:

(for ([i (in-range 0 4 1)])
  (display i))

=> 0123

答案 1 :(得分:2)

我有一个make-iterator程序,使用guile提示来实现spidermonkey生成器(类似但不同于ECMAScript 6生成器)。由于球拍也有提示,这应该可以直接转换为球拍的呼叫 - 继续 - 提示和中止 - 当前 - 继续,而不是guile的呼叫 - 提示和中止 - 提示。

以下是代码:

;; this procedure takes a generator procedure, namely a procedure
;; which has a 'yield' parameter for its first or only argument,
;; followed by such other arguments (other than the one for the
;; 'yield' parameter) as the generator procedure requires, and
;; constructs an iterator from them.  When the iterator is invoked, it
;; will begin executing the procedure unless and until the argument
;; comprising the yield procedure is called, which will cause the
;; iterator to suspend computation and instead return the value passed
;; to yield (yield is a procedure taking one argument).  If invoked
;; again, the iterator will resume computation at the point where it
;; last left off (returning a list of the values, if any, passed to
;; the iterator on resuming).  When the generator procedure has
;; executed to the end, the iterator returns 'stop-iteration.  This
;; procedure is intentionally modelled on javascript/spider-monkey
;; generators.  It has some resemblance to call/ec, except that (i)
;; instead of executing the passed procedure immediately, it returns
;; an iterator which will do so, (ii) it is resumable, and (iii) the
;; procedure to be executed can receive starting arguments in addition
;; to the yield/break argument, to provide an alternative to binding
;; them with a lambda closure.
(define (make-iterator proc . args)
  (define tag (make-prompt-tag))
  (define send-back '())
  (define (thunk)
    (apply proc
           (lambda (val)
             (abort-to-prompt tag val)
             send-back)
           args)
    ;; the generator procedure has returned - reset thunk to do
    ;; nothing except return 'stop-iteration and return
    ;; 'stop-iteration after this last call to proc
    (set! thunk (lambda () 'stop-iteration))
    'stop-iteration)
  (lambda send-args
    (set! send-back send-args)
    (call-with-prompt tag
                      thunk
                      (lambda (cont ret)
                        (set! thunk cont)
                        ret))))

以下是管道衬里的程序:

;; for-iter iterates until the iterator passed to it (as constructed
;; by make-iterator) returns 'stop-iteration.  It invokes the procedure
;; passed as a second argument with the value yielded by the iterator
;; on each iteration.  It is mainly used for composing lazy operations
;; by pipelining, as for example with lazy-map and lazy-filter.
(define (for-iter iter proc)
  (let loop()
    (let ([val (iter)])
      (if (not (eq? val 'stop-iteration))
          (begin
            (proc val)
            (loop))))))

;; lazy-map is a procedure which takes an input iterator constructed
;; by make-iterator and a standard procedure, and then returns another
;; iterator (the output iterator) which yields the values obtained by
;; applying the standard procedure to the input iterator's yielded
;; values.
(define (lazy-map iter proc)
  (make-iterator (lambda (yield)
                   (for-iter iter (lambda (val) (yield (proc val)))))))

;; lazy-filter is a procedure which takes an input iterator
;; constructed by make-iterator, and then returns another iterator
;; (the output iterator) which yields such of the values yielded by
;; the input iterator as are those for which the predicate proc
;; returns #t
(define (lazy-filter iter proc)
  (make-iterator (lambda (yield)
                   (for-iter iter (lambda (val) (if (proc val) (yield val)))))))

以下是第6版犀牛书第280页的规范反例:

(define (counter yield initial)
  (let loop ([next-value initial])
    (let ([increment (yield next-value)])
      (if (not (null? increment))
          (if (eq? (car increment) 'reset)
              (loop initial)
              (loop (+ next-value (car increment))))
          (loop (+ 1 next-value))))))

(define counter-iter (make-iterator counter 10))   ;; create iterator at 10
(display (counter-iter))(newline)                  ;; prints 10
(display (counter-iter 2))(newline)                ;; prints 12
(display (counter-iter 'reset))(newline)           ;; prints 10

我还有一个作为宏的照应版本,它将yield keyname注入代码体,但我更喜欢上面的方法。

修改

对于不支持提示的方案实施,以下内容与使用提示的版本完全相同。但是对于guile,提示比使用完整的call / cc continuation更有效(我猜对于所有实现都不一定如此):

(define (make-iterator proc . args)
  (define prompt-cont #f)
  (define iter-cont #f)
  (define done #f)
  (define (yield arg)
    (call/cc
     (lambda (k)
       (set! iter-cont k)
       (prompt-cont arg))))
  (lambda send-back
    (if done
      'stop-iteration
      (call/cc
       (lambda (k)
         (set! prompt-cont k)
         (if iter-cont
           (iter-cont send-back)
           (begin
              (apply proc yield args)
              (set! done #t)
              (prompt-cont 'stop-iteration))))))))

答案 2 :(得分:2)

经典序列可以在ChezScheme中以几行实现。这是我的版本:

(library (seq)
  (export seq hd tl range smap force-seq for)
  (import (scheme))

  (define-syntax seq
    (syntax-rules ()
      ((_ a b) (cons a (delay b)))))

  (define hd car)
  (define (tl s) (force (cdr s)))

  (define (range-impl a b s)
    (cond ((< a b) (seq a (range-impl (+ a s) b s)))
          (else    '())))


  (define (range a . b)
    (cond ((null? b)       (range-impl 0 a 1))
          ((null? (cdr b)) (range-impl a (car b) 1))
          (else            (range-impl a (car b) (cadr b)))))

  (define (smap f s)
    (cond ((null? s) '())
          (else      (seq (f (hd s)) (smap f (tl s))))))

  (define (force-seq s)
    (when (not (null? s))
      (force-seq (tl s))))

  (define-syntax for
    (syntax-rules ()
      ((_ v r body ...) (force-seq (smap (lambda (v) body ...) r)))))
)

用法:

(import (seq))
(for x (range 5 12)
  (display x)
  (newline))

使用序列可以很容易地以python方式从文件中读取行:

(library (io)
  (export getline lines)
  (import (scheme))
  (import (seq))

  (define (getline ip)
    (define (copy-line)
      (let ((c (get-char ip)))
        (unless (or (eof-object? c)
                    (eqv? c '#\newline))
          (display c)
          (copy-line))))
    (let ((c (peek-char ip)))
      (cond ((eof-object? c) #f)
            (else (with-output-to-string copy-line)))))


    (define (lines ip)
      (let ((l (getline ip)))
        (cond (l    (seq l (lines ip)))
              (else '()))))
)

然后可以写:

(import (seq))
(import (io))

(for l (lines (current-input-port))
  (display l)
  (newline))

答案 3 :(得分:1)

我发现do的执行速度比迭代列表要快得多:

(do ((i 0 (add1 i)))
  ((= i 100000) 'result)
   (some-function! i some-data))

如果您希望功能更强大,则Racket文档建议in-listfor及其变体一起使用。

(for/list ((i (in-list (range 0 100000))))
  (some-function i some-data))