FizzBuzz やってみた

## はじめに

非推奨文章．
今日たまたまQiitaに怪文書乗せると僕がつっつくよ？（意訳）というTweetと，
転職市場ではFizzBuzzがフィルターになるという噂を聞き眠い頭で書いた怪文書．

追記しやすいように，言語別にやっていく．

出力はすべて閲覧用に

```text
(1, 1) (2, 2) (3, FIZZ) (4, 4) (5, BUZZ) (6, FIZZ) (7, 7) (8, 8) (9, FIZZ) (10, BUZZ)
(11, 11) (12, FIZZ) (13, 13) (14, 14) (15, FIZZBUZZ) (16, 16) (17, 17) (18, FIZZ) (19, 19) (20, BUZZ)
(21, FIZZ) (22, 22) (23, 23) (24, FIZZ) (25, BUZZ) (26, 26) (27, FIZZ) (28, 28) (29, 29) (30, FIZZBUZZ)
(31, 31) (32, 32) (33, FIZZ) (34, 34) (35, BUZZ) (36, FIZZ) (37, 37) (38, 38) (39, FIZZ) (40, BUZZ)
(41, 41) (42, FIZZ) (43, 43) (44, 44) (45, FIZZBUZZ) (46, 46) (47, 47) (48, FIZZ) (49, 49) (50, BUZZ)
(51, FIZZ) (52, 52) (53, 53) (54, FIZZ) (55, BUZZ) (56, 56) (57, FIZZ) (58, 58) (59, 59) (60, FIZZBUZZ)
(61, 61) (62, 62) (63, FIZZ) (64, 64) (65, BUZZ) (66, FIZZ) (67, 67) (68, 68) (69, FIZZ) (70, BUZZ)
(71, 71) (72, FIZZ) (73, 73) (74, 74) (75, FIZZBUZZ) (76, 76) (77, 77) (78, FIZZ) (79, 79) (80, BUZZ)
(81, FIZZ) (82, 82) (83, 83) (84, FIZZ) (85, BUZZ) (86, 86) (87, FIZZ) (88, 88) (89, 89) (90, FIZZBUZZ)
(91, 91) (92, 92) (93, FIZZ) (94, 94) (95, BUZZ) (96, FIZZ) (97, 97) (98, 98) (99, FIZZ) (100, BUZZ)

```

となるようにしてある．

## C言語編

static変数は許して……．

実装はすべて以下の環境で試している．

- OS : Arch Linux x64
- Compiler : gcc version 11.1.0 (GCC)

私が書ける程度なのでコンパイラ依存のものは無いと思うが……．

### オーソドックスな方法

普通のFizzBuzz．

```C
#include <stdio.h>

#define MES_F "FIZZ"
#define MES_B "BUZZ"
#define MES_FB "FIZZBUZZ"

char* fizzbuzz(unsigned int num)
{
    static char buf[16]; /* 特に意味はないけど大きめに確保してる */
    if (num % 15 == 0)
    {
        return MES_FB;
    }
    else if (num % 5 == 0)
    {
        return MES_B;
    }
    else if (num % 3 == 0)
    {
        return MES_F;
    }
    else
    {
        sprintf(buf, "%d", num);
        return buf;
    }
}

int main(void)
{
    int i;
    for (i = 1; i <= NUM_MAX; i++)
    {
        printf("(%d, %s)%s", i, fizzbuzz(i), i==NUM_MAX || !(i%10) ? "\n" : " "); /* for view */
        // printf("%s\n", fizzbuzz(i)); /* for test */
    }
    return 0;
}
```

毎回書くの面倒なので定数定義してある．
限界はunsigned intの限界値に依存．
ただし，恐ろしい実装でunsigned intが10進数で16桁以上の数値を表現できる場合はどんな動作するのかわからない．

if文と剰余の演算を用いて大きい方から順に確かめていくよく有るプログラム．
きっかけとなったツイート確認してくとどうやらこれが書けない人がいるということらしい．
IT界隈魔境説を唱えよう．

### if文をやめる

闇の魔術は無いけどちょっと魔術？
`char* fizzbuzz`のみ載せる．

```c
char* fizzbuzz(unsigned int num)
{
    static char buf[16]; /* 特に意味はないけど大きめに確保してる */
    int target = ((num % 3) << 3) + num % 5;
    switch(target)
    {
        case 0:
            return MES_FB;
        case 1: case 2: case 3: case 4:
            return MES_F;
        case 8: case 16:
            return MES_B;
        default:
            sprintf(buf, "%d", num);
            return buf;
    }
}
```

(8/4 編集 ラベル一つ削除)

ifがなくても判断にはswitch文を使えばいい．
ただ，caseでは定数しか書けないのでなんか知らないうちにこうなった．
`target` に3ビットシフトした3での剰余と5での剰余の和を代入することで一つの変数でどの倍数かわかるようになった．
ラベルに式を書ければもう少し楽だけど多分アセンブリ由来のcaseラベルだし致し方なし．

### ラベルを減らしてみる

ラベルいっぱい書くのが面倒だったので．

```c
char* fizzbuzz(unsigned int num)
{
    static char buf[16]; /* 特に意味はないけど大きめに確保してる */
    int target = (num % 3 == 0) + ((num % 5 == 0) << 1);
    switch (target)
    {
        case 3:
            return MES_FB;
        case 1:
            return MES_F;
        case 2:
            return MES_B;
        case 0:
            sprintf(buf, "%d", num);
            return buf;
    }
}
```

### switch文もやめてみる

もういいよね？

```c
char* fizzbuzz(unsigned int num)
{
    static char buf[16]; /* 特に意味はないけど大きめに確保してる */
    int target = (num % 3 == 0) + ((num % 5 == 0) << 1);
    sprintf(buf, "%d", num);
    char * messages[] = {
        buf,
        MES_F,
        MES_B,
        MES_FB,
    };
    return messages[target];
}
```

本質的にはさっきの例となんら変わらない……わけではなく．
配列に格納したアドレスを戻り値を返すか，
ラベルで指定された戻り値を返すかという違いがある．
コンパイラくんが頑張って最適化すると同じようなコードになってる可能性もあるわけだが．

## Python編

環境は，

- OS : Arch Linux x64
- Python : 3.9.6 (default, Jun 30 2021, 10:22:16)

である．

### 普通に

```python
num_max = 100


def fizzbuzz(num):
    if num % 15 == 0:
        return "FIZZBUZZ"
    elif num % 5 == 0:
        return "BUZZ"
    elif num % 3 == 0:
        return "FIZZ"
    else:
        return str(num)


def main():
    for i in range(1, num_max + 1):
        print(f"({i}, {fizzbuzz(i)})" +
              ("\n" if (i == num_max or i % 10 == 0) else ' '), end="")


if __name__ == "__main__":
    main()
```

おとなしく`if` を使ったバージョン．

### `if`を禁句に

```python
def fizzbuzz(num):
    return [
        "FIZZBUZZ", "BUZZ", "FIZZ", str(num),
    ][((num % 3) and 1) + ((num % 5) and 2)]
```

一つの式でできた．

これはPythonの仕様hackと言うべきか，
動的型のスクリプト言語にありがちな論理式の評価結果を用いている．
Pythonの`and`は左辺値が`True`ならば右辺の評価結果を，
そうでなければ（`False`と評価可能な）左辺の値を返す．
これと，Non-Zeroな数値は`True`と評価されることを利用して
3の倍数ならば1を，5の倍数ならば2をそれぞれ足すことでインデックスを取得している．
戻り値を設定した配列の取得したインデックスにある文字列を返り値とすることでFizzBuzzする．

ちなみに`or`は左辺値が`True`ならば左辺値を，
そうでなければ右辺値を返す．