无限的斐波纳契系列,只从列表中取n,不使用变异?

时间:2012-06-24 14:11:08

标签: functional-programming scheme fibonacci

我试图以纯函数的方式解决这个问题,而不使用set!

我已经编写了一个函数,可以永久地为斐波那契数列中的每个数字调用给定的lambda。

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))

我认为这很简洁,但如果我可以缩短它,请赐教:)

使用上面的定义,是否可以编写另一个函数,该函数从斐波那契数列中获取第一个n数字并返回给我一个列表,但没有使用变量变异跟踪状态(我理解的不是真正的功能)。

函数签名不需要与以下内容相同......任何使用each-fib而不使用set!的方法都可以。

(take-n-fibs 7) ; (0 1 1 2 3 5 8)

我猜测有一些延续+我可以使用的currying技巧,但我一直想回到想要使用set!,这是我想要避免的(纯粹用于学习目的/转移)我的想法纯粹是功能性的。)

4 个答案:

答案 0 :(得分:3)

尝试使用惰性代码通过delayed evaluation

实现
(define (each-fib fn)
  (letrec
      ((next (lambda (a b)
               (fn a)
               (delay (next b (+ a b))))))
    (next 0 1)))

(define (take-n-fibs n fn)
  (let loop ((i n)
             (promise (each-fib fn)))
    (when (positive? i)
      (loop (sub1 i) (force promise)))))

如上所述,使用named let可以进一步简化each-fib

(define (each-fib fn)
  (let next ((a 0) (b 1))
    (fn a)
    (delay (next b (+ a b)))))

无论哪种方式,都需要稍微修改each-fib以使用delay原语,这会创建一个承诺:

  

promise 通过force封装要按需评估的表达式。在承诺为force d之后,承诺的每个后期力量都会产生相同的结果。

我想不出一种方法来阻止原始(未修改)过程无限期地迭代。但是,通过上述更改,take-n-fibs可以根据需要继续强制对尽可能多的值进行延迟评估,而不再需要。{/ p>

此外,take-n-fibs现在接收一个打印或依次处理每个值的功能,使用它如下:

(take-n-fibs 10 (lambda (n) (printf "~a " n)))
> 0 1 1 2 3 5 8 13 21 34 55

答案 1 :(得分:1)

您提供了斐波那契元素的迭代函数。如果你想要,而不是迭代每个元素,累积结果,你应该使用一个不同的原语fold(或reduce)而不是{ {1}}。
可能可以使用continuation将iter转换为iter,但使用{{}的直接解决方案可能会降低可读性并降低效率1}}或变异。)

但请注意,使用通过变异更新的累加器也没问题,只要您了解自己在做什么:为方便起见,您在本地使用可变状态,但从外部看,函数fold是观察性纯粹,所以你不要“污染”你的程序作为一个整体有副作用。

fold的快速原型,改编自您自己的代码。我对“何时停止折叠”做出了任意选择:如果函数返回take-n-fibs,我们返回当前累加器而不是继续折叠。

fold-fib

最好有一个更强大的约定来结束折叠。

答案 2 :(得分:1)

我写了几个变种。首先你问是否

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))

可以写得更短。该模式经常被使用,因此引入了称为named let的特殊语法。您的函数使用名为let的函数看起来像这样:

(define (each-fib fn)
  (let next ([a 0] [b 1])
    (fn a)
    (next b (+ a b))))

为了使控件从一个函数流向另一个函数,可以在支持TCO的语言中使用延续传递样式。每个函数都有一个额外的参数,通常称为k(用于延续)。函数k表示接下来要做什么。

使用这种风格,可以按如下方式编写程序:

(define (generate-fibs k)
  (let next ([a 0] [b 1] [k k])
    (k a (lambda (k1) 
           (next b (+ a b) k1)))))

(define (count-down n k)
  (let loop ([n n] [fibs '()] [next generate-fibs])
    (if (zero? n)
        (k fibs)
        (next (λ (a next)
                (loop (- n 1) (cons a fibs) next))))))

(count-down 5 values)

现在手动编写风格有点烦人,所以可以 方便介绍合作例程。打破不使用set!的规则我选择使用共享变量fibs,其中generate-fibs重复地将新的斐波那契数字包含在其中。当倒计时结束时,count-down例程只读取值。

(define (make-coroutine co-body)
  (letrec ([state (lambda () (co-body resume))]
           [resume (lambda (other)
                     (call/cc (lambda (here)
                                (set! state here)
                                (other))))])
    (lambda ()
      (state))))

(define fibs '())

(define generate-fib
  (make-coroutine 
   (lambda (resume)
     (let next ([a 0] [b 1])
       (set! fibs (cons a fibs))
       (resume count-down)
       (next b (+ a b))))))

(define count-down
  (make-coroutine   
   (lambda (resume)
     (let loop ([n 10])
       (if (zero? n)
           fibs
           (begin
             (resume generate-fib)
             (loop (- n 1))))))))

(count-down)     

你得到一个带有沟通线程的版本的奖金:

#lang racket
(letrec ([result #f]
         [count-down 
          (thread 
           (λ ()
             (let loop ([n 10] [fibs '()])
               (if (zero? n)
                   (set! result fibs)
                   (loop (- n 1) (cons (thread-receive) fibs))))))] 

         [produce-fibs
          (thread
           (λ ()
             (let next ([a 0] [b 1])
               (when (thread-running? count-down)
                 (thread-send count-down a)
                 (next b (+ a b))))))])
  (thread-wait count-down)
  result)

线程版本是Racket特定的,其他版本应该在任何地方运行。

答案 3 :(得分:0)

建立一个列表很难。但显示结果仍然可以完成(以非常糟糕的方式)

#lang racket

(define (each-fib fn)
  (letrec
    ((next (lambda (a b)
             (fn a)
             (next b (+ a b)))))
    (next 0 1)))



(define (take-n-fibs n fn)
  (let/cc k
        (begin
          (each-fib (lambda (x) 
                      (if (= x (fib (+ n 1)))
                          (k (void))
                          (begin
                            (display (fn x))
                            (newline))))))))


(define fib
  (lambda (n)
    (letrec ((f
              (lambda (i a b)
                (if (<= n i)
                    a
                    (f (+ i 1) b (+ a b))))))
      (f 1 0 1))))

请注意,我使用普通的斐波那契函数作为逃避(就像我说的那样,非常糟糕)。我想没人会推荐像这样的编程。

无论如何

(take-n-fibs 7 (lambda (x) (* x x))) 
0
1
1
4
9
25
64