Генерация чисел Фибоначчи в Haskell?

Как в Haskell я могу сгенерировать числа Фибоначчи на основе того свойства, что n-е число Фибоначчи равно (n-2) -ому числу Фибоначчи плюс (n-1) -ое число Фибоначчи?

Я видел это:

fibs :: [Integer]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

Я действительно не понимаю этого, или как он создает бесконечный список вместо одного, содержащего 3 элемента.

Как мне написать код haskell, который работает, вычисляя фактическое определение, а не делая что-то действительно странное с функциями списка?


person Lucky    schedule 09.07.2009    source источник
comment
Вам не хватает всего удовольствия от Haskell, если вы избегаете странных функций списков. Но как бы то ни было, в приведенном выше коде есть хорошее объяснение того, как работает рекурсия: scienceblogs.com/goodmath/2006/11/   -  person rtperson    schedule 10.07.2009
comment
Статья, на которую ссылается @rtperson, теперь находится в научных блогах. ru / goodmath / 2006/11/28 /.   -  person Ben    schedule 03.01.2016
comment
Есть альтернативное определение Haskell для серии Фибоначчи, которое, я думаю, было бы легче проанализировать: | fibSerie a b = a : (fibSerie b (a+b)), а затем: fibs = fibSerie 1 1.   -  person jpmarinier    schedule 26.07.2019
comment
ω = 2 + min ω (ω - 1). zipWith создает здесь (бесконечный) список целых чисел, а не только одно целое число, так что это не 2 + 1 общие элементы, а 2 + ω. то есть ω.   -  person Will Ness    schedule 20.08.2019


Ответы (11)


Вот другая и более простая функция, которая вычисляет n-е число Фибоначчи:

fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

Реализация, о которой вы говорите, передает некоторые наблюдения о том, как значения в Фибоначчи соотносятся друг с другом (и как Haskell может определять структуры данных в терминах самих себя, фактически создавая бесконечные структуры данных)

Функция в вашем вопросе работает так:

Предположим, у вас уже есть бесконечный список чисел Фибоначчи:

   [ 1, 1, 2, 3, 5,  8, 13, .... ]

tail в этом списке

   [ 1, 2, 3, 5, 8, 13, 21, .... ]

zipWith объединяет два списка поэлементно с использованием данного оператора:

   [ 1, 1, 2, 3,  5,  8, 13, .... ]
+  [ 1, 2, 3, 5,  8, 13, 21, .... ]
=  [ 2, 3, 5, 8, 13, 21, 34, .... ]

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

Теперь, чтобы получить n-е число Фибоначчи, достаточно получить n-й элемент бесконечного списка чисел Фибоначчи:

fib n = fibs !! n

Прелесть Haskell в том, что он не вычисляет ни один элемент списка чисел Фибоначчи до тех пор, пока он не понадобится.

Я заставил твою голову взорваться? :)

person dtb    schedule 09.07.2009
comment
Мне это нравится - рассчитайте список, суммируя соответствующие значения списка, который вы пытаетесь вычислить. Мой мозг обычно так не работает - это все равно что пытаться заглянуть в собственное ухо. - person Steve B.; 09.07.2009
comment
fib 0 = 1 должно быть fib 0 = 0. Я заметил это только потому, что в эту секунду совершил ту же ошибку. Ха-ха. - person Christopher Done; 06.02.2010
comment
@Christopher иногда первый 0 в последовательности опускается. - person Yacoby; 12.03.2010
comment
@Yacoby Его определение выше неверно. fib (0) = 0, а не 1. Это важно. fib (2) = 1, определение выше дает fib (2) = 2. Это потому, что fib (n) = fib (n-1) + fib (n-2). Итак, согласно приведенному выше определению, fib (2) = fib (1) + fib (0) = 1 + 1 = 2, что неверно. Это портит всю последовательность. - person Christopher Done; 13.03.2010
comment
@Christoper Нет, это не так. Последовательность просто сдвинута влево на 1. Ничего особенного. - person Yacoby; 13.03.2010
comment
@dtb Прав ли я, предполагая, что это работает с использованием хвостовой рекурсии? - person abarax; 05.09.2011
comment
@Abarax Нет, на самом деле хвостовая рекурсия сделала бы трюк невозможным. Это лень и осторожная рекурсия, рекурсивный вызов находится на каждом шаге в поле конструктора fibo : recursive_call, поэтому для его достижения мы должны деконструировать результат предыдущего вызова. Таким образом, глубина рекурсии никогда не превышает 1. - person Daniel Fischer; 19.01.2012
comment
Стоит отметить, что это определение делает больше добавлений, чем необходимо: O (fib n) сложений. Решению zipWith требуется только O (n). - person occulus; 29.12.2012
comment
Упс, я вижу, что yairchu рассказал об этом ниже. - person occulus; 29.12.2012
comment
Я до сих пор не понимаю, откуда Haskell берет бесконечные списки, даже если вычисляет только первое число Фибоначчи. Сейчас я использую два бесконечных списка, насколько я понимаю, и добавить их не очень сложно, но откуда они берутся? Мне все равно придется их вычислять, что займет примерно столько же времени, сколько и вычисление числа Фибоначчи с простой рекурсией - где я что-то здесь упускаю? - person Zelphir Kaltstahl; 07.02.2016
comment
@Zelphir, который займет примерно то же время, что и вычисление числа Фибоначчи с простой рекурсией, неверно, поскольку вычисление фибоначчи с рекурсией делает O (fib n) сложений, что является МАССИВНЫМ (экспоненциальным). Преимущество метода списка заключается в том, что последовательные вызовы одного и того же значения фибоначчи (fib(3) в fib(2) + fib(3), fib(3) + fib(4)) по существу кэшируются в самом бесконечном списке, поэтому он становится больше похожей на мемоизированную рекурсивную функцию, которая включает O (n) добавлений. - person semicolon; 10.02.2016
comment
@semicolon Я получаю аргумент O (n) и O (fib n), но это предполагает, что у вас уже есть бесконечный список. Так откуда у вас этот бесконечный список? Все это звучит так, как будто где-то в Haskell что-то уже подготовлено, но похоже, что этого нет, иначе я бы просто использовал этот бесконечный список и получил n-й элемент. Как бы вы тогда вычисляли бесконечные списки и при этом оставались бы ниже O (fib n)? - person Zelphir Kaltstahl; 11.02.2016
comment
@Zelphir Вы создаете бесконечный список с 0 : 1 : zipWith (+) fibs (tail fibs). Вы начинаете с [0, 1...] и добавляете к нему zipWith (+) fibs (tail fibs). Первый элемент fibs - это 0, а первый элемент хвостовых fibs - это 10 so the next element is 0 + 1 = 1`, что дает вам [0, 1, 1...], и теперь вы получаете второй элемент zipWith ..., который 1 + 1 = 2 дает вам [0, 1, 1, 2...] и так далее. - person semicolon; 11.02.2016
comment
@semicolon Спасибо, я думаю, теперь понял, прочитав scienceblogs.com/goodmath/2006/11/28/. Раньше я не понимал, что вы написали, но теперь это яснее: уловка заключается в тех двух уже существующих элементах 0 и 1, которые затем всегда добавляются, чтобы сохранить результаты предыдущих вычислений. Думаю, это снова компромисс между пространством и временем. Либо используйте больше памяти, чтобы сохранить все ранее рассчитанные значения, либо вычисляйте снова и снова с другим подходом (не наивным с O (fib n)). - person Zelphir Kaltstahl; 11.02.2016
comment
Для тех, кто просматривает эти комментарии и все еще находится в недоумении, может быть полезен ответ на следующий вопрос. - person Enlico; 12.02.2020
comment
Отличный ответ. - person Iceland_jack; 09.11.2020
comment
Эта функция не обрабатывает отрицательные числа как входные данные. - person Yan.F; 06.04.2021

согласно определению, каждый элемент ряда Фибоначчи является суммой двух предыдущих членов. вставив это определение в ленивый haskell, вы получите это!

fibo a b = a:fibo b (a+b)

теперь просто возьмите n элементов из fibo, начиная с 0,1

take 10 (fibo 0 1)
person renjith    schedule 06.02.2014
comment
т.е. a, b = (0,1) : (b, a+b) или в Haskell map fst $ ((\(a,b)->(b,a+b)) iterate` (0,1)) `. :) - person Will Ness; 07.02.2014
comment
для fibs = map fst $ iterate (\(a,b) -> (b,a+b)) (0,1) см. wiki.haskell.org/The_Fibonacci_sequence#With_iterate - person Wolf; 17.07.2017
comment
Какова вычислительная сложность по сравнению с fibs = 0 : 1 : zipWith (+) fibs (tail fibs)? - person Wolf; 17.07.2017
comment
Это прекрасная функция, а красота - все в математике и программировании. Замечательна простота и убедительность. Он поэтичен, компактен и полон смысла. - person fp_mora; 07.03.2018

Чтобы расширить ответ dtb:

Между «простым» решением есть важное отличие:

fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

И тот, который вы указали:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

Простое решение занимает O (1.618 N N) времени. для вычисления N-го элемента, в то время как тот, который вы указали, принимает O (N 2). Это потому, что указанное вами значение принимает во внимание, что вычисления fib n и fib (n-1) (которые необходимы для его вычисления) разделяют зависимость fib (n-2), и что его можно вычислить один раз для обоих, чтобы сэкономить время. O (N 2) - для N сложений чисел по O (N) цифр.

person yairchu    schedule 09.07.2009
comment
@newacct: Если тебе нужны только выдумки !! n, вам нужно вычислить все из n выдумок, n элементов, с вычислением O (n) каждое, потому что добавление двух чисел из O (n) цифр равно O (n). - person yairchu; 10.07.2009
comment
@newacct: вы предполагаете, что каждое отдельное динамическое вхождение fib k (где k - константа) объединено в один преобразователь. GHC может быть достаточно умен, чтобы сделать это в данном случае, но я не думаю, что это гарантировано. - person Chris Conway; 10.07.2009
comment
хорошо, я неправильно понял вопрос. я вижу, вы уже сказали то, что я пытался сказать - person newacct; 10.07.2009
comment
Почему бы просто не сказать «золотое сечение» (Фи) вместо неточного 1.618? - person Zelphir Kaltstahl; 11.02.2016
comment
@Zelphir: для этого потребуется, чтобы читатели также были знакомы с золотым сечением. Для этого аргумента точность не критична. - person yairchu; 15.02.2016

здесь существует ряд различных алгоритмов Haskell для последовательности Фибоначчи. «Наивная» реализация похожа на то, что вам нужно.

person Richard Dunlap    schedule 09.07.2009

fibs :: [Integer]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

сначала с fibs и tail fibs мы можем получить 3-й элемент:

fibs                        : [1, 1, ?
tail fibs                   : [1, ?
zipWith (+) fibs (tail fibs): [2, ?

теперь, когда мы знаем, что 3-е равно 2, мы можем получить 4-е:

fibs                        : [1, 1, 2, ?
tail fibs                   : [1, 2, ?
zipWith (+) fibs (tail fibs): [2, 3, ?

теперь 5-е:

fibs                        : [1, 1, 2, 3, ?
tail fibs                   : [1, 2, 3, ?
zipWith (+) fibs (tail fibs): [2, 3, 5, ?

и так далее ..

person nichijou    schedule 24.07.2019

Определение fibonaci (n):

fibonacci (n) = fibonacci (n-1) + fibonacci (n-2)

Наивная реализация на Haskell

fibonacci :: Integer -> Integer
fibonacci 0 = 1
fibonacci 1 = 1
fibonacci x = fibonacci (x-1) + fibonacci (x-2)

Все формулы можно проследить до этого определения, некоторые из которых работают очень быстро, а некоторые очень медленно. В приведенной выше реализации O (n) = 2 ^ n

В духе вашего вопроса, позвольте мне убрать использование списков и дать вам то, что работает в O (n) Т.е. давайте не будем держать все фибоначчи от 0 до n в списке.

Если у нас есть тройка (кортеж из трех членов), который выглядит так:

(n, fibonacci[n-1], fibonacci[n])

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

(n+1, fibonacci[n], fibonacci[n-1] + fibonacci[n]) = (n+1, fibonacci[n], fibonacci[n+1])

И следующая тройка из последней тройки: (n+2, fibonacci[n+1], fibonacci[n] + fibonacci[n+1]) = (n+1, fibonacci[n+1], fibonacci[n+2])

И так далее ...

n = 0 => (0,0,1) 
n = 1 => (1,1,1) - calculated from the previous triple
n = 2 => (2,1,2) - calculated from the previous triple
n = 3 => (3,2,3) - calculated from the previous triple
n = 4 => (4,3,5) - calculated from the previous triple
n = 5 => (5,5,8) - calculated from the previous triple

Давайте реализуем это в Haskell и будем использовать понятные имена переменных:

nextTripleIfCurrentNIsLessThanN :: (Int, Integer, Integer) -> Int -> (Int, Integer, Integer)
nextTripleIfCurrentNIsLessThanN (currentN, x, y) n = if currentN < n
then nextTripleIfCurrentNIsLessThanN (currentN + 1, y, x + y) n
else (currentN, x, y)

thirdElementOfTriple :: (x,y,z) -> z
thirdElementOfTriple (x,y,z) = z

fibonacci :: Int -> Integer
fibonacci n = thirdElementOfTriple (nextTripleIfCurrentNIsLessThanN (0,0,1) n)

Это будет работать в O (n) [Это умеренно квадратично, что проявляется в больших количествах. Причина в том, что добавление больших чисел обходится дороже, чем добавление маленьких. Но это отдельное обсуждение модели вычислений.]

fibonacci 0
1
fibonacci 1
1
fibonacci 2
2
fibonacci 3
3
fibonacci 4
5
fibonacci 5
8
fibonacci 5000
6276302800488957086035253108349684055478528702736457439025824448927937256811663264475883711527806250329984690249846819800648580083040107584710332687596562185073640422286799239932615797105974710857095487342820351307477141875012176874307156016229965832589137779724973854362777629878229505500260477136108363709090010421536915488632339240756987974122598603591920306874926755600361865354330444681915154695741851960071089944015319300128574107662757054790648152751366475529121877212785489665101733755898580317984402963873738187000120737824193162011399200547424034440836239726275765901190914513013217132050988064832024783370583789324109052449717186857327239783000020791777804503930439875068662687670678802914269784817022567088069496231111407908953313902398529655056082228598715882365779469902465675715699187225655878240668599547496218159297881601061923195562143932693324644219266564617042934227893371179832389642895285401263875342640468017378925921483580111278055044254198382265567395946431803304304326865077742925818757370691726168228648841319231470626
person galeaspablo    schedule 09.11.2017

используя итерацию

fibonaci = map fst (iterate f (0,1)) where f (x,y) = (y,x+y)

с использованием

take 10 fibonaci

[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377]
person jmejia    schedule 19.04.2014

Ленивый способ создания бесконечных рядов Фибоначчи может быть легко достигнут unfoldr следующим образом;

fibs :: [Integer]
fibs = unfoldr (\(f,s) -> Just (f,(s,f+s))) (0,1)
person Redu    schedule 10.05.2017

LOL, я люблю сопоставление с образцом в Haskell, но оно бесполезно в стандартных функциях Фибоначчи. Стандартный список строится справа. Чтобы использовать сопоставление с образцом и минусы, список должен быть построен слева. Что ж, одно утешение, по крайней мере, в том, что это действительно быстро. ~ O (n), так и должно быть. Вспомогательная функция необходима для переворота бесконечного списка (вещи, которые вы можете делать только в Haskell, радость), и эта функция выводит каждый последующий список выполнения, поэтому «последний» также используется в конвейере вспомогательных функций.

f (x:y:xs) = (x+y):(x:(y:xs))

Помощник

fib n = reverse . last . take n $ iterate f [1,0]

Это версия со списком, и, я думаю, она объясняет, как создается список, что и является целью. Я хочу сделать кортежную версию.

Изменить 15.03.2018

Во-первых, Уилл Несс просветил меня, зная, что весь список, генерируемый на каждой итерации, не нужен, и что нужны только два последних значения, а значения для списка результатов являются первыми значениями каждого сгенерированного списка или пары. Это было так смешно. После того, как Уилл сказал мне, что значения для списка были первыми значениями списков, я запустил его и увидел значения 0,1,1,2,3,5,8,13 в качестве каждого заголовка каждого списка, я сказал WTF, Изменил ли мой код на моем ПК? Ценности были, но как !? Через некоторое время я понял, что они были там все время, но просто не видел их. фу. Версия функции и вспомогательной функции Уилла:

f = (\(x:y:xs) -> (x+y):x:xs) -- notice, no y: put back only x+y & x

и его вспомогательная функция перезаписывает

fib n = map head . take n $iterate f [0,1]

Я тоже думаю, что теперь их можно комбинировать:

fib n = take n . map head $ iterate (\(x:y:xs) -> (x+y):x:xs) [0,1]

Кроме того, функция может быть и с кортежами.

fib n = take n . map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)

Другая форма, форма понимания списка, также может быть написана для всех:

fib n = take n [ fst t | t <- iterate (\(a,b) -> (b,a+b)) (0,1)]

Все они итеративны и надежны. Самая быстрая - карта со списками за 12,23 секунды для fib 5000. На втором месте по скорости распознавания кортежей - 13,58 секунды для fib 5000.

person fp_mora    schedule 11.03.2018
comment
Требуется настоящее видение, чтобы увидеть, что в заголовке каждого сгенерированного списка были нужные числа. Мне не хватает этого видения. Большое вам спасибо! Этот урок выходит далеко за рамки этой проблемы и вашего глубокого понимания ее. Тем не менее, я воспринимаю map fst $ iterate g (1,0) как восхитительный юмор. Версия кортежа действительно заменяет f. Также в fibs = map head $ iterate f [1,0] с использованием [0,1] в качестве параметра получается 0 в качестве заголовка выходного списка take n $ map head $ iterate f [0,1] «У меня нет рабочего понятия кортежной версии, но да, лень в языке лучше, чем мороженое. Почти. - person fp_mora; 13.03.2018
comment
попробуйте mapM_ print $ take 15 $ iterate f [1,0]. Теперь измените f на f (x:y:xs) = (x+y):(x:xs) и попробуйте снова эту строку mapM_ ... и сравните результаты. - person Will Ness; 13.03.2018
comment
хотите, чтобы вас поразила лень, попробуйте ps n = q where q = scanl (\\) [2..n] [[p,p+p..n] | p <- map head q], затем попробуйте map head $ ps 100 или map head $ ps 555. вам может потребоваться import Data.List, чтобы сначала получить (\\). Чтобы узнать, что там происходит, попробуйте mapM_ print $ ps 100. - person Will Ness; 13.03.2018
comment
@Will Ness - волшебник. Он улучшил мой код извинения с помощью f (x: y: xs) = (x + y) :( x: xs), который намного чище. Его переработка вспомогательной функции - map head $ take 24 $ iterate f [0,1], что также намного чище. Лень Haskell предотвращает любые потери производительности для выразительной ясности. Я новичок в Haskell, так что дорожу этим сайтом и замечательными людьми B / c Уилла Несса, я только что использовал монаду и скоро смогу изучить оператор '\\' и сканирование, которого я тоже никогда не делал Уилл Несс, кем я был действительно искал был f. f. f ... f (x) Использование комбинатора Y Это должно быть мило - person fp_mora; 13.03.2018
comment
Основное различие между двумя функциями Y из Haskell Wiki. Рекурсию можно заменить на fix: fibs = fix (scanl (+) 0. (1 :)) fibs = fix ((0 :). Scanl (+) 1 Исправление, используемое здесь, должно быть реализовано путем совместного использования, fix f = xs, где xs = f xs, а не репликации кода, fix f = f (fix f), чтобы избежать квадратичного поведения. - person fp_mora; 14.03.2018

Вставьте код, ваше определение

fib :: Int -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
  -- i.e.
  -- fib (n+2) = fib (n+1) + fib n

Int -> a ~= [a] потому что

from f = map f [0..]     -- from :: (Int -> a) -> [a]
to = (!!)                -- to :: [a] -> (Int -> a)

Таким образом

fibs :: [Integer]
fibs = from fib 

fibs !! 0 = 1
fibs !! 1 = 1
fibs !! (n+2)    = fibs !! (n+1)     +  fibs !! n
-- or,
drop 2 fibs !! n = drop 1 fibs !! n  +  fibs !! n
                 = zipWith (+) (tail fibs) fibs !! n
-- i.e.
take 2 fibs = [1,1]
drop 2 fibs = zipWith (+) (tail fibs) fibs
-- hence, 
fibs = take 2 fibs ++ drop 2 fibs
     = 1 : 1 : zipWith (+) (tail fibs) fibs

Или как a, b = (0,1) : (b, a+b):

fibs :: [Integer]
fibs = a
  where
  (a,b) = unzip $ (0,1) : zip b (zipWith (+) a b)
person Will Ness    schedule 12.11.2019

Я делал домашнее задание6 по CIS194 и обнаружил, что вы можете писать так. Для вычисления первых n элементов требуется всего O (n) операций сложения.

fibs2 :: [Integer]
fibs2 = [0, 1] ++ [fibs2 !! (n-1) + fibs2 !! (n-2) | n <- [2..]]
person Lily Zhang    schedule 09.11.2020