web-dev-qa-db-ja.com

Haskellでフィボナッチ数を生成しますか?

Haskellでは、n番目のフィボナッチ数が(n-2)番目のフィボナッチ数と(n-1)番目のフィボナッチ数に等しいというプロパティに基づいてフィボナッチ数を生成するにはどうすればよいですか?

私はこれを見ました:

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

私はそれを本当に理解していない、またはそれが3つの要素を含むリストの代わりに無限リストを生成する方法を理解していません。

リスト関数で本当に奇妙なことをするのではなく、実際の定義を計算することで機能するhaskellコードをどのように書くのですか?

48
Lucky

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は、指定された演算子を使用して、要素ごとに2つのリストを結合します。

   [ 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の利点は、必要になるまでフィボナッチ数のリストの要素を計算しないことです。

私はあなたの頭を爆発させましたか? :)

81
dtb

定義によれば、フィボナッチ数列のすべての項目は、前の2つの用語の合計です。この定義をlazy haskellに入れると、これが得られます!

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

今、0,1から始まるfiboからn個のアイテムを取得します

take 10 (fibo 0 1)
24
renjith

Dtbの答​​えを拡張するには:

「単純な」ソリューションには重要な違いがあります。

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

そして、指定したもの:

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

簡単な解決策は O(1.618NN) N番目の要素を計算する時間、指定した要素はO(N2)。これは、指定したものが_fib n_とfib (n-1)の計算(計算に必要)がfib (n-2)の依存関係を共有し、計算できることを考慮しているためです。時間を節約するために両方に1回。オン2)は、O(N)桁の数字のN個の加算用です。

20
yairchu

フィボナッチ数列には多くの異なるHaskellアルゴリズムがあります ここ 。 「単純な」実装は、あなたが望んでいるもののように見えます。

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

最初は、fibstail 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, ?

等々 ..

2
nichijou

無限のフィボナッチ数列を生成する怠zyな方法は、次のようにunfoldrによって簡単に実現できます。

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

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までのすべてのフィボナッチを保持しないようにしましょう。

トリプルがある場合(3つのメンバーを持つタプル):

(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
1
galeaspablo

この実装は、100,000番目のフィボナッチ数をほぼ瞬時に計算します。

fib = fastFib 1 1

fastFib _ _ 0 = 0
fastFib _ _ 1 = 1
fastFib _ _ 2 = 1
fastFib a b 3 = a + b
fastFib a b c = fastFib (a + b) a (c - 1)
1

反復を使用して

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]
0
jmejia

LOL、私はHaskellのパターンマッチングが大好きですが、標準のフィボナッチ関数では役に立たなくなります。標準リストは右から作成されます。パターンマッチングとコンスを使用するには、リストを左から作成する必要があります。まあ、少なくとも1つの慰めは、これが本当に速いことです。 〜O(n)、あるはずです。無限リスト(Haskellでしかできないこと、喜び)を反転するにはヘルパー関数が必要です。この関数は、実行の後続の各リストを出力するため、ヘルパー関数パイプラインでも「最後」が使用されます。

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

ヘルパー

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

これはリストバージョンであり、リストの作成方法を説明していると思います。これが目的です。タプル版をやりたいです。

編集3/15/2018

まず、Will Nessは、各反復で生成されるリスト全体が不要であり、使用された最後の2つの値のみが必要であり、結果リストの値は生成された各リストまたはペアの最初の値であるという知識を教えてくれました。とても面白かった。ウィルがリストの値がリストの最初の値であると私に言った後、私はそれを実行し、各リストの各ヘッドとして値0,1,1,2,3,5,8,13を見ました、私はWTF、 PCでコードを変更しましたか?値はありましたが、どのように!?しばらくして、私は彼らがずっとそこにいることに気づきましたが、私は彼らを見ませんでした。うん。関数とヘルパー関数のウィルのバージョンは次のとおりです。

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)]

これらはすべて反復的で堅牢です。最も速いのはfib 5000で12.23秒のリストを持つマップです。タプルの理解度は13.58秒でfib 5000の2番目に速いものでした。

0
fp_mora