【问题标题】:How to implement Python-style generator in Scheme (Racket or ChezScheme)?如何在 Scheme(Racket 或 ChezScheme)中实现 Python 样式的生成器?
【发布时间】:2014-10-05 19:37:59
【问题描述】:

今天我使用 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))

【问题讨论】:

  • 您可以在my answercall-with-current-continuation - state saving concept 中找到解决方案。它不是完全重复,但它展示了如何实现一个在连续调用中返回连续值的函数。
  • @Joshua Taylor 谢谢。我会研究它。
  • 当我更详细地阅读您的问题时,我认为 Óscar 提供的简单解决方案可能足以满足您的特定需求。我提供的链接更多地涉及标题“如何在 Scheme 中实现 Python 样式生成器”中的一般问题。对于一个简单的范围生成器来说,这很容易,因为生成过程的状态是如此简单。对于更复杂的事情,最好有更充分的延续力量。
  • 生成器相当于惰性列表。在 Scheme 中定义非记忆的、SICP 样式的惰性列表非常容易,它们足以解决许多问题——我也冒险猜测皇后问题。看下半场of this answer

标签: scheme


【解决方案1】:

在这种情况下使用延续(如链接中所建议)是不合理的。这是一个更简单的想法:让我们将生成器定义为一个 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

【讨论】:

    【解决方案2】:

    我有一个使用 guile 提示的 make-iterator 过程来实现 spidermonkey 生成器(与 ECMAScript 6 生成器相似但不完全相同)。由于球拍也有提示,这应该可以直接翻译成球拍的呼叫连续提示和中止当前继续代替诡计的呼叫提示和中止提示。

    代码如下:

    ;; 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)))))))
    

    以下是 Rhino 书第 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 键名注入到代码体中,但我更喜欢上面的方法。

    编辑:

    对于不支持提示的方案实现,以下与使用提示的版本相同。然而,使用诡计,提示比使用完整的调用/抄送延续更有效(我猜这不一定适用于所有实现):

    (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))))))))
    

    【讨论】:

      【解决方案3】:

      经典序列可以在 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))
      

      【讨论】:

        【解决方案4】:

        我发现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))
        

        【讨论】:

          猜你喜欢
          • 2012-02-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-27
          • 1970-01-01
          • 1970-01-01
          • 2012-07-08
          • 1970-01-01
          相关资源
          最近更新 更多