Генерация рядов Фибоначчи в Lisp с помощью рекурсии?

Я новичок в LISP. Я пытаюсь написать функцию в CLISP для генерации первых n чисел ряда Фибоначчи.

Это то, что я сделал до сих пор.

(defun fibonacci(n)
  (cond
    ((eq n 1) 0)
    ((eq n 2) 1)
    ((+ (fibonacci (- n 1)) (fibonacci (- n 2))))))))

программа выводит N-е число рядов Фибоначчи. Я пытаюсь изменить его так, чтобы он печатал серию, а не только N-й термин.

возможно ли это сделать только в одной рекурсивной функции, используя только основные функции?

3 ответов


да:

(defun fibonacci (n &optional (a 0) (b 1) (acc ()))
  (if (zerop n)
      (nreverse acc)
      (fibonacci (1- n) b (+ a b) (cons a acc))))

(fibonacci 5) ; ==> (0 1 1 2 3)

логика заключается в том, что вам нужно знать два предыдущих номера для создания следующего.

a     0 1 1 2 3 5 ...
b     1 1 2 3 5 8 ...
new-b 1 2 3 5 8 13 ...     

вместо того, чтобы возвращать только один результат, я аккумулирую все a - s до n равна нулю.

редактировать без обратного это немного более неэффективно:

(defun fibonacci (n &optional (a 0) (b 1))
  (if (zerop n)
      nil
      (cons a (fibonacci (1- n) b (+ a b)))))

(fibonacci 5) ; ==> (0 1 1 2 3)

программа выводит N-е число рядов Фибоначчи.

эта программа ничего не печатать. Если вы видите вывод, это, вероятно, потому, что вы вызываете его из read-eval-печати-loop (REPL), который читает форму, оценивает ее, а затем печать результат. Е. Г., можно будет делать:

CL-USER> (fibonacci 4)
2

если вы завернули этот вызов во что-то еще, вы увидите, что он не печатается что угодно:

CL-USER> (progn (fibonacci 4) nil)
NIL

как вы это написали, будет трудно изменить его, чтобы напечатать каждое число Фибоначчи только один раз, так как вы делаете много избыточных вычислений. Например, вызов

(fibonacci (- n 1))

вычислит (fibonacci (- n 1)), но так же будет прямой вызов

(fibonacci (- n 2))

это означает, что вы, вероятно, не хотите, чтобы каждый вызов fibonacci для печати всей последовательности. Если вы это сделаете, обратите внимание, что (print x) возвращает значение x, так что вы можете попросту:

(defun fibonacci(n)
  (cond
    ((eq n 1) 0)
    ((eq n 2) 1)
    ((print (+ (fibonacci (- n 1)) (fibonacci (- n 2)))))))
CL-USER> (progn (fibonacci 6) nil)

1 
2 
1 
3 
1 
2 
5 
NIL

вы увидите некоторые повторяющиеся части там, так как есть избыточные вычисления. Однако вы можете вычислить ряд намного эффективнее, начав с первых двух чисел и подсчитав:

(defun fibonacci (n)
  (do ((a 1 b)
       (b 1 (print (+ a b)))
       (n n (1- n)))
      ((zerop n) b)))
CL-USER> (fibonacci 6)

2 
3 
5 
8 
13 
21 

возможность сохранить базовую структуру, которую вы использовали, - передать дополнительный флаг функции, которая сообщает, хотите ли вы печатать или нет:

(defun fibo (n printseq)
  (cond
    ((= n 1) (if printseq (print 0) 0))
    ((= n 2) (if printseq (print 1) 1))
    (T
     (let ((a (fibo (- n 1) printseq))
           (b (fibo (- n 2) NIL)))
       (if printseq (print (+ a b)) (+ a b))))))

идея заключается в том, что когда вы делаете два рекурсивных вызова только в первом, вы передаете флаг о выполнении печати, а во втором вызове вместо этого просто передаете ноль, чтобы избежать печати снова.