How can I generate series of Pell numbers instead of a specific one in Lisp

前端 未结 3 1639
清酒与你
清酒与你 2021-01-21 15:27

How do I use cons or other way to print a list of Pell numbers till the Nth number?

(defun pellse (k)
   (if (or (zerop k) (= k 1))
       k
   (+ (* 2 (pellse (         


        
相关标签:
3条回答
  • 2021-01-21 15:44

    Here is how to do it in a way that won’t be exponential:

    (defun pells (n)
      (loop repeat n
        for current = 0 then next
        and next = 1 then (+ (* 2 next) current)
        collect current))
    

    The time complexity to calculate the nth element given the two previous elements is O(log(Pn)) where Pn is the nth Pell number; you need log(Pn) bits for the answer and log(Pn) operations for the addition. We don’t actually need to work out what Pn is: It is defined by a simple linear recurrence relation so the solution must be exponential so log(Pn) = O(n). Therefore the complexity of calculating the first n Pell numbers is O(n*n) = O(n2).

    One cannot[*] do better than O(n2) as one must write O(n2) bits to represent all these numbers.

    [*] Although I very much doubt this, it might, in theory, be possible to represent the list in some more compact way by somehow sharing data.

    0 讨论(0)
  • 2021-01-21 15:49

    One possible solution would be to use the LOOP macro of Common Lisp, e.g.:

    (print
        (loop for x in '(1 2 3 4 5 6 7)
          for y = (pellse x)
          collect y))
    

    That prints out the following result:

    (1 2 5 12 29 70 169)
    

    Based on this, you can build the following function:

    (defun list-of-n-pell-numbers (n)
        (loop for x from 0 to n
              for y = (pellse x)
              collect y))
    

    And run it like the following:

    (print (list-of-n-pell-numbers 7))
    (0 1 2 5 12 29 70 169)
    

    But please be careful when using this code, because your definition of pellse function is recursive, and has the risk of a stack overflow: make it call itself repeatedly enough (e.g. for big values of N), and it might blow up the call stack, unless you do some tail recursion. You might want to check the following explanations:

    • http://www.lispworks.com/documentation/lcl50/aug/aug-51.html
    • https://0branch.com/notes/tco-cl.html
    0 讨论(0)
  • 2021-01-21 15:56

    Here is an approach to solving this problem which works by defining an infinite stream of Pell numbers. This is based on the ideas presented in SICP, and particularly section 3.5. Everyone should read this book.

    First of all we need to define a construct which will let us talk about infinite data structures. We do this by delaying the evaluation of all but a finite part of them. So start with a macro called delay which delays the evaluation of a form, returning a 'promise' (which is a function of course), and a function called force which forces the system to make good on its promise:

    (defmacro delay (form)
      ;; Delay FORM, which may evaluate to multiple values.  This has
      ;; state so the delayed thing is only called once.
      (let ((evaluatedp-n (make-symbol "EVALUATEDP"))
            (values-n (make-symbol "VALUES")))
        `(let ((,evaluatedp-n nil) ,values-n)
           (lambda ()
             (unless ,evaluatedp-n
               (setf ,evaluatedp-n t
                     ,values-n (multiple-value-list
                                (funcall (lambda () ,form)))))
             (values-list ,values-n)))))
    
    (defun force (promise)
      ;; force a promise (delayed thing)
      (funcall promise))
    

    (This implementation is slightly overcomplex for our purposes, but it's what I had to hand.).

    Now we'll use delay to define streams which are potentially infinite chains of conses. There are operations on these corresponding to operations on conses but prefixed by stream-, and there is an object called null-stream which corresponds to () (and is in fact the same object in this implementation).

    (defmacro stream-cons (car cdr)
      ;; a cons whose cdr is delayed
      `(cons ,car (delay ,cdr)))
    
    (defun stream-car (scons)
      ;; car of a delayed cons
      (car scons))
    
    (defun stream-cdr (scons)
      ;; cdr of a delayed cons, forced
      (force (cdr scons)))
    
    (defconstant null-stream
      ;; the empty delayed cons
      nil)
    
    (defun stream-null (stream)
      ;; is a delayed cons empty
      (eq stream null-stream))
    

    Now define a function pell-stream which returns a stream of Pell numbers. This function hand-crafts the first two elements of the stream, and then uses a generator to make the rest.

    (defun pell-stream ()
      ;; A stream of Pell numbers
      (labels ((pell (pn pn-1)
                 (let ((p (+ (* 2 pn) pn-1)))
                   (stream-cons p (pell p pn)))))
        (stream-cons 0 (stream-cons 1 (pell 1 0)))))
    

    And now we can simply repeatedly takes stream-cdr to compute Pell numbers.

    (defun n-pell-numbers (n)
      (loop repeat n
            for scons = (pell-stream) then (stream-cdr scons)
            collect (stream-car scons)))
    

    And now

    > (n-pell-numbers 20)
    (0
     1
     2
     5
     12
     29
     70
     169
     408
     985
     2378
     5741
     13860
     33461
     80782
     195025
     470832
     1136689
     2744210
     6625109)
    

    Note that, in fact, pell-stream can be a global variable: it doesn't need to be a function:

    (defparameter *pell-stream*
      (labels ((pell (pn pn-1)
                 (let ((p (+ (* 2 pn) pn-1)))
                   (stream-cons p (pell p pn)))))
        (stream-cons 0 (stream-cons 1 (pell 1 0)))))
    
    (defun n-stream-elements (stream n)
      (loop repeat n
            for scons = stream then (stream-cdr scons)
            collect (stream-car scons)))
    

    If we define a little benchmarking program:

    (defun bench-pell (n)
      (progn (n-stream-elements *pell-stream* n) n))
    

    Then it's interesting to see that this is clearly essentially a linear process (it slows down for later elements because the numbers get big and so operations on them take a long time), and that the stateful implementation of promises makes it much faster after the first iteration (at the cost of keeping quite a lot of bignums around):

    > (time (bench-pell 100000))
    Timing the evaluation of (bench-pell 100000)
    
    User time    =        2.020
    System time  =        0.803
    Elapsed time =        2.822
    Allocation   = 1623803280 bytes
    441714 Page faults
    100000
    
    > (time (bench-pell 100000))
    Timing the evaluation of (bench-pell 100000)
    
    User time    =        0.007
    System time  =        0.000
    Elapsed time =        0.006
    Allocation   = 1708248 bytes
    0 Page faults
    100000
    
    0 讨论(0)
提交回复
热议问题