Вот один из способов сделать это, который дает вам немного времени для ленивых последовательностей, хотя, безусловно, это не совсем оптимальный способ вычисления последовательности Фибоначчи.
Учитывая определение последовательности Фибоначчи, мы можем видеть, что она построена путем многократного применения одного и того же правила к базовому случаю '(1 1)
. Функция Clojure iterate
звучит так, как будто для этого подойдет:
user> (doc iterate)
-------------------------
clojure.core/iterate
([f x])
Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects
Таким образом, для нашей функции мы бы хотели получить что-то, что будет принимать значения, которые мы вычислили до сих пор, суммирует два самых последних и возвращает список нового значения и всех старых значений.
(fn [[x y & _ :as all]] (cons (+ x y) all))
Список аргументов здесь просто означает, что x
и y
будут связаны с первыми двумя значениями из списка, переданного в качестве аргумента функции, список, содержащий все аргументы после первых двух, будет связан с _
и исходный список, переданный в качестве аргумента функции, может быть передан через all
.
Теперь, iterate
вернет бесконечную последовательность промежуточных значений, поэтому для нашего случая мы захотим обернуть это во что-то, что просто вернет интересующее нас значение; Ленивое вычисление остановит всю бесконечную последовательность, вычисляемую.
(defn fib [n]
(nth (iterate (fn [[x y & _ :as all]] (cons (+ x y) all)) '(1 1)) (- n 2)))
Обратите внимание, что это возвращает результат в порядке, обратном вашей реализации; это легко исправить с помощью reverse
, конечно.
Редактировать: или действительно, как говорит Амаллой, используя векторы:
(defn fib [n]
(nth (iterate (fn [all]
(conj all (->> all (take-last 2) (apply +)))) [1 1])
(- n 2)))