0

Lucas numbers are similar to Fib numbers except it starts with a 2 instead of a 1:

 2, 1, 3, 4, 7, 11, 18,

I want to write a function to produce a list of Lucas series numbers in decreasing order until the nth element.

I wrote this but I traced it and it is way too slow to implement into my list-building function.

(defun lucas (n)
  (cond 
    ((equal n 0) 2)
    ((equal n 1) 1)
    (t (+ (lucas (- n 1))
          (lucas (- n 2))))))

Here is what I wrote for the list function. The problem is (lucas n) is very slow and I don't want to have to call a helper function when I'm already using let.

(defun lucas-list(n)
  (cond
   ((equal n 0) (cons n nil))
   (t (let ((n1 (lucas n)) (n2 (lucas(- n 1))))
        (cons n1 (lucas-list n2))))))

What I am trying to achieve:

(lucas-list 3) => '(4 3 1 2))

Any suggestions/help is appreciated

user3063864
  • 661
  • 2
  • 7
  • 18

4 Answers4

3

The (pseudo-)linear time algorithm for the Fibonacci numbers can easily be extended to the Lucas numbers:

(define (lucas n)
  (let loop ((a 2) (b 1) (n n))
    (if (= n 0)
      a
      (loop b (+ a b) (- n 1)))))

This can be mapped over the integers to get the desired result:

> (map lucas '(0 1 2 3 4 5))
(2 1 3 4 7 11)
> (reverse (map lucas '(0 1 2 3 4 5)))
(11 7 4 3 1 2)

But there's actually a faster way: if you realize that the above algorithm computes Lᵢ₋₁ and Lᵢ₋₂ before it computes Lᵢ, then saving those in a list should save a lot of time. That gives:

(define (lucas n)
  (let loop ((a 2) (b 1) (n n) (l '()))
    (if (= n 0)
      l
      (loop b (+ a b) (- n 1) (cons a l)))))

In action:

> (lucas 20)
(9349 5778 3571 2207 1364 843 521 322 199 123 76 47 29 18 11 7 4 3 1 2)
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • Thanks.. I'm not familiar with the let syntax you used though. In lisp it doesn't let me define a local function name, only variables. – user3063864 Dec 06 '13 at 21:09
  • 1
    @user3063864: you had tagged the question Scheme, so I thought Scheme code would be in order. This is called a [named let](http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme-Z-H-8.html#node_sec_6.2), and it translates quite directly to a `letrec` or a `do` loop. – Fred Foo Dec 06 '13 at 21:18
1

An elegant way to optimise fibonacci-type procedures is with a memoïze function which caches every previously calculated result:

In Racket (using hash tables; generic, scales very well)

(define (memoize fn)
  (let ((cache (make-hash)))
    (lambda arg (hash-ref! cache arg (lambda () (apply fn arg))))))

In R6RS Scheme (less efficient and less generic, but still excellent for this purpose)

(define (memoize proc)
  (let ([cache '()])
    (lambda (x)
      (cond
        [(assq x cache) => cdr]
        [else (let ([ans (proc x)])
                (set! cache (cons (cons x ans) cache))
                ans)])))) 

Applied to the lucas procedure (this works like a Python decorator):

(define lucas
  (memoize
   (lambda (n)
     (cond
       ((= n 0) 2)
       ((= n 1) 1)
       (else   (+ (lucas (- n 1)) (lucas (- n 2))))))))

and the list procedure, taking advantage of the fact that using an accumulator reverses the result, becomes:

(define (lucas-list n)
  (let loop ((i 0) (res null))
    (if (= i n)
        res
        (loop (+ i 1) (cons (lucas i) res)))))

Test:

(display (lucas-list 20))
=> {9349 5778 3571 2207 1364 843 521 322 199 123 76 47 29 18 11 7 4 3 1 2}
uselpa
  • 18,732
  • 2
  • 34
  • 52
0

How about:

(defun lucas-list (n)
  (if (equal n 0)
      '()
      (cons (lucas n) (lucas-list (- n 1)))))
GoZoner
  • 67,920
  • 20
  • 95
  • 145
0
(defun lucas-list-decreasing (n &aux (table (make-hash-table)))
  (labels ((lucas-int (n)
              (or (gethash n table)
                  (setf (gethash n table)
                        (cond ((equal n 0) 2)
                              ((equal n 1) 1)
                              (t (+ (lucas-int (- n 1))
                                    (lucas-int (- n 2)))))))))
    (lucas-int n)
    (loop for i downfrom (1- n) downto 0
          collect (gethash i table))))

Or:

(defun lucas (n &aux (table (make-hash-table)))
  (labels ((lucas-int (n)
             (or (gethash n table)
                 (setf (gethash n table)
                       (cond ((equal n 0) 2)
                             ((equal n 1) 1)
                             (t (+ (lucas-int (- n 1))
                                   (lucas-int (- n 2)))))))))
    (values (lucas-int n)
            table)))

(defun lucas-list-decreasing (n)
  (multiple-value-bind (value table)
      (lucas n)
    (declare (ignore value))
    (loop for i downfrom (1- n) downto 0
          collect (gethash i table))))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Wouldn't you have to run your loop from 0 upwards so that memoization is the most effective? – uselpa Dec 06 '13 at 23:48
  • @uselpa: the loop only retrieves. – Rainer Joswig Dec 07 '13 at 00:14
  • Nice but it doesn't work if you declare lucas-int as a top-level function. – uselpa Dec 07 '13 at 00:22
  • Correct me if I'm wrong, but if the memoize hash table is local to a top-level *lucas-int*, then a top-level *lucas-int-decreasing* would not have access to it. – uselpa Dec 07 '13 at 09:29
  • @uselpa: why not move it outside, too? – Rainer Joswig Dec 07 '13 at 09:34
  • Yes technically speaking you could, but I wouldn't because it pollutes the namespace. I like the way you can do this in Racket (*generic* memoize that works with every function; hash table that is only accessible locally) and I was just wondering if Common Lisp allowed for an equally elegant solution. – uselpa Dec 07 '13 at 09:39
  • @uselpa: why would it pollute the namespace? There are different options. – Rainer Joswig Dec 07 '13 at 09:45