フィボナッチ数列とは
漸化式
$n_{0}=0$
$n_{1}=1$
$n_{a+2} = n_{a}+n_{a+1} (a = 0,1,2,3...)$
で表される数列。噛み砕いて言うと
数列のどの数も、その数の前の2つの数字を足した数になる数列ただし最初の2項は0と1。
0,1,1,2,3,5,8,13...
この数列を任意の個数まで出力しようというのです。
問題
任意のn
個(ただし固定の項を考慮して$n \geqq 2$)のフィボナッチ数列を配列で出力しましょう。
安直な実装
>>> def fibonacci(n):
... result = [0, 1]
... while len(result) < n:
... result.append(result[-1] + result[-2])
... print(result)
...
>>> fibonacci(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
これを1行で書こうというのです。
書き換える
Pythonに限らず多くの言語は1つの処理を記述するのに何通りかの書き方があり、置き換えと組み合わせで行数を減らすというアプローチを取ることになります。
while
ブロック
Pythonではforループ、if分岐をそれぞれ1行で書く方法があります。
それぞれリスト内包表記と三項演算子です。
リスト内包表記はforについての記法なので、そのままではwhile
ブロックに適用できません。
そこでwhile
をfor
とif
で置き換えます。
while len(result)<n:
result.appendresult[-1]+result[-2]
print(result)
for i in range(n):
if len(result) < n:
result.appendresult[-1]+result[-2]
else:
print(result)
while
の、まず条件記述はif
で簡単に置き換えられますね。
問題はループ部分ですが、n
回回すようにしてしまいましょう。なぜならn
は常に2以上で、
-
n
=2の場合:if
ブロックはFalse
になりappend
はされず、初期状態のresult=[0,1]
が出力される。 -
n
>2の場合:if
ブロックはn
回回り、n-2
回append
されて、初期状態の2と合わせて2+(n-2)=n
個の配列が出力される。
破綻は起きないからです。
if
ブロック
Pythonは三項演算子でif
を1行で書くことができます。
if len(result) < n:
result.append(result[-1]+result[-2])
else:
print(result)
result.appendresult[-1]+result[-2]) if len(result) < n else print(result)
処理部分がやや長くなっていて見づらいですが、構造は下記のとおりです。
if 条件:
処理1
else:
処理2
#三項演算子を使うと
処理1 if 条件 else 処理2
for
ブロック
for
の置き換えはみんな大好きリスト内包表記です。
for i in range(n):
result.append(result[-1]+result[-2]) if len(result) < n else print(result)
[result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)]
こちらも構造を書いておきます
なお、上記コードのループ内で使っているのはresult
だけで、i
は使っていません。Pythonでは使わない添字は_
で書くことが慣例なのでそうしています。
for i in イテラブル:
iに対する処理
# リスト内包表記
[iに対する処理 for i in イテラブル]
ループ回数の調整
今までにできたコードを実行してみましょう。
def fibonacci(n):
result=[0,1]
[result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)]
fibonacci(10)
>>>[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
result
は初期状態で[0,1]
を持っているので、len(result)
はn
より2大きいままループし、長さがnに達したあとも2回余分にループを回り、その時はelse
節に飛びます。そのうち1回目を出力に使うのですが、2回目は無駄なので、ループ用range
内のn
は1つ引いてやります。
def fibonacci(n):
result=[0,1]
[result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n-1)]
fibonacci(10)
>>>[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
def
ブロック
def
を無名関数にしちゃいましょう。1行で関数を定義できます。
result
を一旦外出しにして、関数処理部分をlambda
に置き換えます。
関数呼び出し部分も削りましょう。無名なので呼び出せませんから。
result=[0,1]
lambda n: [result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)]
さらに、lambda全体をカッコでくくり、末尾にカッコで括った引数を与えてやると、定義と同時に引数を与えて実行、ということができます。
(lambda 引数の名前:処理)(実際に入れる引数)
今回はn=10
を代入してみましょう。
result=[0,1]
(lambda n: [result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)])(10)
>>> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
lambdaは通常関数と同じように複数の引数をコンマで区切って設定できます。初期値result
をlambda引数に入れてしまいましょう。
余計なNone配列の除去
(lambda result, n: [result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n-1)])([0,1],10)
コメントでご指摘いただき気づきました。
これをインタプリタで実行すると、
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[None, None, None, None, None, None, None, None, None]
余計なNone
配列がその後に出力されてしまいます。
お恥ずかしながら筆者はpyファイルに記述したワンライナーコードを実行しながら執筆していたため今の今まで気づきませんでした。
原因
原因はおそらくインタプリタが、実行された`lambda`そのものの戻り値を`print`してしまうことです。 もう少し詳しく言うと、 まず**`lambda`の処理の過程で** `[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]` が出力されます。ここまでは正常な動作なのですが、このあとインタプリタはlambdaの戻り値を出力しようとします。 `lambda`の戻り値となる配列は**`result.append()`という破壊的関数の実行結果**です。破壊的メソッドは新しい配列を返さず`None`を返す、つまり`None`が格納されている配列が`lambda`の戻り値となるわけで、インタプリタはこの戻り値を出力してしまったというわけです。これを修正するために、lambda
の戻り値をresult
そのものにし、結果をprint
するようにします。
print((lambda result, n: [result.append(result[-1]+result[-2]) if len(result) < n else result for _ in range(n-1)] and result)([0,1],10))
print
なしだと、インタプリタでは動きますがファイルで実行した場合出力されません。
セイウチ演算子を使った簡略化
こちらもコメントで教えていただきました。
print((lambda n:[a:=0,b:=1,*[b:=a+(a:=b)for _ in '_'*n]][:n])(20))
- 初期値
[a,b]=[0,1]
となる配列を用意して、 -
n
を引数に取り、0からn
まで、 - 配列の最後に
b=a+b
を$n_{x}$回繰り返した配列の$n_{x}$個目を配列末尾に追加して -
n
に至ったらprint
する
でしょうか。
無理やり言語化しようとしましたが反ってわかりづらくなってしまったかもしれませんしもしかしたら説明自体が間違っているかもしれません。