【Python】FizzBuzz問題のカッコイイ解答を読み解いてみる

## はじめに

現在僕が受講しているPython基礎を習得する講習にて、

**「FizzBuzz問題を２通り以上の書き方で書いてみよう！」**

という課題がありました。

その模範解答の１つとして講師の方が挙げてくださった解答を目にして、基礎を学習中の身の僕はとても感動を覚えました。

こちらがその解答例。

```python:FizzBuzzのカッコイイ解答
end = 100
for i in range(1, end + 1):
    print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
```

う、うつくしい…
たった３行で書けるとは。（もっと言うと2行でいけてる）

けど **ちょっと何を言っているのかわからない。**

悔しいので、この記述を読み解いてみることにしました。
これまでどこかでは見たことのある色んなエッセンスが詰まっていて、読み解けた時には程よい満足感に包まれました。

この記事では、「このスクリプトが何を言っているのかわかる」というところを目指して、解説してみます。
ぜひ一緒に読み解いてみましょう！

※文法の正確な解説は参考リンクとして貼らせていただいて、まずは「意味がわかる」ところを目指します。
※自分も理解が浅い部分があるため、何か間違いあればご指摘お願いします！

## FizzBuzz問題

```text:FizzBuzz
数字を１から順に１００までログ出力します。ただし、
・数字が３の倍数の時には数字の代わりに「Fizz」
・数字が５の倍数の時には数字の代わりに「Buzz」
・数字が３の倍数かつ５の倍数の時には代わりに「FizzBuzz」

と出力するようにしてください。
```


もしこの問題をやったことない方は、まずはこれをスクリプトで書いてみることに挑戦しましょう！
いろんな入門書、アルゴリズムの本で登場します。

今回の記事は、ひとまずこの問題に解答できる、という前提で書いているので、その上で読み進めてみてください。
（`for`文や`if`文の基本的な使い方ができていれば良いかと思います）


## オーソドックスな解答例

一応、FizzBuzz問題のオーソドックスな解答例です。
色々な書き方があるので、この限りではありません。

```python:FizzBuzz解答例
end = 100
for i in range(1, end + 1):
    if i % 3 == 0 and i % 5 == 0:
        print('FizzBuzz', end='\n')
    elif i % 3 == 0:
        print('Fizz', end=' ')
    elif i % 5 == 0:
        print('Buzz', end=' ')
    else:
        print(i, end=' ')
```

学んだプチテクニック：

- `print()` 関数に `end`オプションをつけて文字列を指定することで、その文字列（`\n`改行、`\t`タブなども可）を表示させて見やすくできます。
- 「FizzBuzz」が出力される15の倍数ごとに改行することで見やすくなる、という学びがあったため使ってみました。


## 今回のカッコイイ解答について読み解く

冒頭に書いたカッコイイ解答ですが、３行目がちょっと何言ってるかわからない。

```python:よくわからん３行目のスクリプト
print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
```


調べながら読み解いていくと、この一文には、

- （１）文字列のスライス `'hogehoge'[a:b]`
- （２）三項演算子`x if 条件式 else y`
- （３）文字列・数値の論理演算`x or y`

このあたりがポイントとして含まれていそうです。

...
...と、その前に前提知識を。


### （０）前提知識：数値・文字列の真偽値
必要な知識として **「数値・文字列の真偽値」** について整理しておきます。

数値を条件式として使った場合には、TrueかFalseの真偽値で返ります。そしてその真偽については、

- 数値0, 空の文字列の時 => False
- それ以外の時 => True

となります。`if`文の条件式などで使うことでコードがシンプルになるので、慣れておきたいですね。


### （１）文字列のスライス
先ほどの一文をさらに分解して、わかりそうなところから見ていきましょう！

```python
'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] 
```

この部分は、細かい部分は無視して構造だけ書くと、

`'FizzBuzz'[a:b]`

と言う形になっており、これは **「文字列のスライス」** を適用しています。

今回の`FizzBuzz`という文字列にスライスを適用することで、

```python
print('FizzBuzz'[0:8]) #① => FizzBuzz
print('FizzBuzz'[0:4]) #② => Fizz 
print('FizzBuzz'[4:8]) #③ => Buzz
print('FizzBuzz'[4:4]) #④ => (何も表示なし)
```

と上手いこと表現できます。

まず、これが第一のポイント。


### （２）条件式（三項演算子）`if`-`else`
次に読解したいのは、`'FizzBuzz'[a:b]`と書いた時の`a`,`b`に当たる

```python
(4 if i % 3 else 0)
(4 if i % 5 else 8)
```

この記法です。

なんとなく「後置`if`」という表現を聞いたことがあったのですが、これは **「条件式」**または**「三項演算子」** と呼ぶらしいです。
[6. 式 (expression) — Python 3.8.3 ドキュメント](https://docs.python.org/ja/3/reference/expressions.html#conditional-expressions)

使う分には **if〜elseを１行で書ける書き方** と抑えておけば良さそうでしょうか。

参考：
[三項演算子(Python) - Qiita](https://qiita.com/howmuch515/items/bf6d21f603d9736fb4a5)


これを当てはめると、上の２つの書き方は

`(4 if i % 3 else 0)`
=> 「i%3の値がTrue（iを3で割った余りの値が0以外、つまり3で割り切れない時)なら4、それ以外（3で割り切れる時）は0」

`(4 if i % 5 else 8)`
=>「iを5の値がTrue（iを5で割った余りの値が0以外、つまり35で割り切れない時)なら4、それ以外（3で割り切れる時）は8」

と、条件によって変わる数値を表す表現になります。
（0は条件式の中で扱うと、真偽値は`False`になるんでしたね（前提条件））


**なるほどわからん。**

...
...具体的に今回の問題の`FizzBuzz`で考えてみると、これまでの話がなんとなく繋がって見えてきます。

`i`が3の倍数か、5の倍数か、3の倍数かつ5の倍数（15の倍数）かで場合分けができて、

```python
print('FizzBuzz'[0:8]) #①=> FizzBuzz（iが3の倍数かつ5の倍数の時）
print('FizzBuzz'[0:4]) #② => Fizz（iが3の倍数で、5の倍数ではない時）
print('FizzBuzz'[4:8]) #③ => buzz（iが5の倍数で、3の倍数ではない時）
print('FizzBuzz'[4:4]) #④ => (何も表示なし)（iが3の倍数でも5の倍数でもない時）
```

この4パターンの結果を

`'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)]`

この１行で表現することができるのです。

ただこれだけだとまだ、`Fizz`, `Buzz`, `FizzBuzz`の文字列は出力できても、数字を出力することができません。

そこで、次のポイントです。

### （３）数値・文字列の論理演算

ここまできたらもう一息です。もう一つ見ていない部分がありました。

```python:よくわからん３行目のスクリプト
print('FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i)
```
この最後の`or i`という表現に謎が残っています。
そこでまた細かい部分を省いて構造だけ書いてみると、

`print('FizzBuzz'[a:b] or i)`

という構造になっています。

この`or`は **「論理演算子」** というやつですが、どうもなんかこの表現の中では直感的にわかる`or`の使い方と様子が違いそうです。

一般的な論理演算子`or`の使い方は

`条件式 or 条件式`　=> `True`か`False`を返す
例) `1 < 10 or 2 > 20`　=>　`True`

このように、`or`で並ぶのは条件式で、１つでも合っていれば`True`、どれも間違っていたら`False`が返る、と言う真偽値を返す演算となっています。
これは直感的にわかりやすいですね。

ところが今回の表現は、

`文字列・数値 or 文字列・数値`　=> どちらかの値（文字列か数値）を返す
例) `'FizzBuzz' or 3`　=> `FizzBuzz`
例) `'' or 3`　=> 3

という風に`or`で並んでいる値のどれかを返す演算になっています。上の例では
「`or`の前の値が真・後ろの値が真のとき、全体としては前の値を返す」
「`or`の前の値が偽・後ろの値が真のとき、全体としては後ろの値を返す」
というルールとなっています。
[6. 式 (expression) — Python 3.8.3 ドキュメント](https://docs.python.org/ja/3/reference/expressions.html#boolean-operations)

参考：
[Python♪論理演算子（and, or, not）の使い方と数値や文字列の論理演算 \| Snow Tree in June](https://snowtree-injune.com/2018/08/14/bool/)

**なるほどわからん。**

...
...細かい文法は今後学んでいくとして、今回は読み解くことを優先して「このようなルールがあるんだなー」と流すことにします。
（「短絡評価（ショートサーキット）」という評価法？が使われています）

具体的に今回の問題に当てはめてみると、(2)で考えた４パターンをさらに以下のように考えることができます。

```python
print('FizzBuzz'[0:8] or i) #① => FizzBuzz（orの前の値が真・後ろの値が真の時、前の値を返す）
print('FizzBuzz'[0:4] or i) #② => Fizz（上と同様）
print('FizzBuzz'[4:8] or i) #③ => buzz（上と同様）
print('FizzBuzz'[4:4] or i) #④ => i（orの前の値が偽・後ろの値が真のとき、後ろの値を返す）
```

①〜③の出力結果は（２）の時と同じですが、④だけ出力結果が変わっています。

`'FizzBuzz'[(4 if i % 3 else 0):(4 if i % 5 else 8)] or i`

と`or`を使うだけで、１行で数値を出力することが可能になったのです。



### おわりに

楽しかった。以上です。
ここまで読んでみて、改めて冒頭のカッコイイ`FizzBuzz`解答をのスクリプトを実行してみると、少し見え方が違うのではないでしょうか。

なかなか直感的に説明できないのがもどかしく感じたので、精進せねばと思いました。
今回見てきた文法は一つ一つは結構目にする書き方かと思うので、こういうのを使いこなして、自分もpythonでシンプルなスクリプトを書けるようになりたいものです。

`FizzBuzz`面白いですね。
また新しい概念を学んだら、あえてそれを生かした`FizzBuzz`を作ってみる、と言うのも練習として面白そうだなあと思いました。