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

## フィボナッチ数列とは
漸化式
$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$)のフィボナッチ数列を配列で出力しましょう。

## 安直な実装

```py
>>> 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`で置き換えます。

```py
while len(result)<n:
        result.appendresult[-1]+result[-2]
    print(result)
```

```py
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行で書くことができます。

```py
    if len(result) < n:
        result.append(result[-1]+result[-2])
    else:
        print(result)
```


```py
    result.appendresult[-1]+result[-2]) if len(result) < n else print(result)
```


処理部分がやや長くなっていて見づらいですが、構造は下記のとおりです。

```py
if 条件:
    処理1
else:
    処理2
#三項演算子を使うと
処理1 if 条件 else 処理2
```

### `for` ブロック
`for`の置き換えはみんな大好きリスト内包表記です。

```py
for i in range(n):
    result.append(result[-1]+result[-2]) if len(result) < n else print(result)
```

```py
[result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)]
```

こちらも構造を書いておきます
なお、上記コードのループ内で使っているのは`result`だけで、`i`は使っていません。Pythonでは使わない添字は`_`で書くことが慣例なのでそうしています。

```py
for i in イテラブル:
    iに対する処理
# リスト内包表記
[iに対する処理 for i in イテラブル]
```

### ループ回数の調整

今までにできたコードを実行してみましょう。

```py
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つ引いてやります。

```py
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`に置き換えます。
関数呼び出し部分も削りましょう。無名なので呼び出せませんから。

```py
result=[0,1]
lambda n: [result.append(result[-1]+result[-2]) if len(result) < n else print(result) for _ in range(n)]
```

さらに、lambda全体をカッコでくくり、末尾にカッコで括った引数を与えてやると、定義と同時に引数を与えて実行、ということができます。

```py
(lambda 引数の名前:処理)(実際に入れる引数)
```

今回は`n=10`を代入してみましょう。

```py
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配列の除去
```py
(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ファイルに記述したワンライナーコードを実行しながら執筆していたため今の今まで気づきませんでした。
<details><summary>原因</summary>
原因はおそらくインタプリタが、実行された`lambda`そのものの戻り値を`print`してしまうことです。
もう少し詳しく言うと、
まず**`lambda`の処理の過程で**
`[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]`
が出力されます。ここまでは正常な動作なのですが、このあとインタプリタはlambdaの戻り値を出力しようとします。
`lambda`の戻り値となる配列は**`result.append()`という破壊的関数の実行結果**です。破壊的メソッドは新しい配列を返さず`None`を返す、つまり`None`が格納されている配列が`lambda`の戻り値となるわけで、インタプリタはこの戻り値を出力してしまったというわけです。</details>

これを修正するために、`lambda`の戻り値を`result`そのものにし、結果を`print`するようにします。

```py
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`なしだと、インタプリタでは動きますがファイルで実行した場合出力されません。

## セイウチ演算子を使った簡略化

こちらもコメントで教えていただきました。

```py
print((lambda n:[a:=0,b:=1,*[b:=a+(a:=b)for _ in '_'*n]][:n])(20))
```
1. 初期値`[a,b]=[0,1]`となる配列を用意して、
1. `n`を引数に取り、0から`n`まで、
1. 配列の最後に`b=a+b`を$n_{x}$回繰り返した配列の$n_{x}$個目を配列末尾に追加して
1. `n`に至ったら`print`する

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