1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

フィボナッチ数列で始めるPythonワンライナー -ブロックの置き換えで攻略せよ-

Last updated at Posted at 2021-10-22

フィボナッチ数列とは

漸化式
$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ブロックに適用できません。
そこでwhileforifで置き換えます。

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-2appendされて、初期状態の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))
  1. 初期値[a,b]=[0,1]となる配列を用意して、
  2. nを引数に取り、0からnまで、
  3. 配列の最後にb=a+bを$n_{x}$回繰り返した配列の$n_{x}$個目を配列末尾に追加して
  4. nに至ったらprintする

でしょうか。
無理やり言語化しようとしましたが反ってわかりづらくなってしまったかもしれませんしもしかしたら説明自体が間違っているかもしれません。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?