# 縛りFizzBuzz (Easy) - yukicoder を解く
この記事は [茨大 Advent Calendar 2020](https://adventar.org/calendars/5043) 23日目の記事です.
茨大 Advent Calendar 2020の4日目にも[記事](https://hackmd.io/@ibarakicisUnofficial/HkcLn00Kw)を書きましたが,アドベントカレンダー最終盤のこの時期に再登板です.今回はyukicoderの[縛りFizzBuzz (Easy)](https://yukicoder.me/problems/no/3022)を解いてみます.折りたたみ多めでお送りしますがご容赦ください.
## 問題概要
整数$N \: (1 \leq N \leq 100)$が与えられる.$1$から$N$までの整数$i$を順番に出力せよ.1つ出力するごとに改行せよ.ただし,
* $i$が$3$の倍数であれば,$i$の代わりに`Fizz`を出力せよ.
* $i$が$5$の倍数であれば,$i$の代わりに`Buzz`を出力せよ.
* $i$が$3$の倍数かつ$5$の倍数であれば,$i$の代わりに`FizzBuzz`を出力せよ.
また,**ソースコード中で数字(`0`, `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`のいずれかの文字)が使われている場合は不正解となる.**
## 解法らしきもの
<details><summary>ヒントらしきもの(クリックで展開)</summary>
<div>
「ソースコード中で数字を使ってはいけない」という制約さえなければ,ただのFizzBuzzです.加算や乗算,剰余の演算子は自由に使えるので,(整数型の)`0`や`1`が代入された変数を作れれば,必要な数値はそこから生成できます.数字を使わずに数値`0`, `1`を作るまでの道筋はプログラミング言語によります.
</div>
</details>
## C/C++で解いてみる
まずはC言語で解いてみます.C言語での解答を少し書き換えればC++ らしいコードになるので,C++での解答例は省略します.
<details><summary>ヒントらしきもの(クリックで展開)</summary>
<div>
C/C++の場合はインクリメント演算子を使えます.数値`0`が代入された変数さえ用意できれば,そこから`1`以上の整数が代入された変数を作れます.したがって,数値`0`が代入された変数を用意できればこの問題は解けたも同然です.
</div>
</details>
<details><summary>解答方針(クリックで展開)</summary>
<div>
C言語の場合,整数型のグローバル変数は自動的に`0`で初期化されます.これで数値`0`が代入された変数は用意できます.あとはインクリメント演算子(`++`)を使って整数値`3`, `5`, `15`が代入された変数を用意し,普通のFizzBuzzのように解きます.
グローバル変数は使いたくないという人は,頭に`static`をつけてint型の変数を宣言しましょう.この場合も変数は自動的に`0`で初期化されます.
</div>
</details>
<details><summary>ソースコード(クリックで展開)</summary>
<div>
```
#include <stdio.h>
int zero;// 整数型のグローバル変数は自動的に0で初期化される
int main(void){
int three, five, fifteen, n, i;
// 0の代入とインクリメントで3が代入された変数をつくる
three = zero;
three++;
three++;
three++;
// 3の代入とインクリメントで5が代入された変数をつくる
five = three;
five++;
five++;
// 3と5があるので15は掛け算1回でつくれる
fifteen = five * three;
scanf("%d", &n);
// ループカウンタに1をセット
i = zero;
i++;
// いつものFizzBuzz
for(; i <= n; i++){
if(i % fifteen == zero){
printf("%s\n", "FizzBuzz");
}else if(i % three == zero){
printf("%s\n", "Fizz");
}else if(i % five == zero){
printf("%s\n", "Buzz");
}else{
printf("%d\n", i);
}
}
return zero;
}
```
グローバル変数の代わりに`static`を使う場合はこんな感じになります.
```
#include <stdio.h>
int main(void){
static int zero;// これも自動的に0で初期化される
int three, five, fifteen, n, i;
// 以下略
```
</div>
</details>
## Pythonで解いてみる・C/C++で解いてみる(更に縛りプレイ)
せっかくなので,Pythonでも解いてみましょう.また,C/C++で書くときも制限を増やしてみます.
<details><summary>C/C++で書くときの制約条件(クリックで展開)</summary>
<div>
* グローバル変数,`static`は使用禁止
* インクリメント演算子も使用禁止
</div>
</details>
<details><summary>ヒントらしきもの(クリックで展開)</summary>
<div>
Pythonにはインクリメント演算子がないので,数値`0`が代入された変数の他に数値`1`が代入された変数も用意しないと,1以上の整数が代入された変数を作るのは難しいかと思います.
</div>
</details>
### 強引な解法(Python)
<details><summary>解答方針(クリックで展開)</summary>
<div>
Pythonの`for`文(というよりは`range`関数)を使えば,数字の`0`を書かなくても整数値`0`が代入された変数を作れます.`1`が代入された変数も,`for`文を強引に制御して作ります.
あとは数字の`0`, `1`が代入された変数を使って整数値`3`, `5`が代入された変数を用意し,普通のFizzBuzzのように解きます.
</div>
</details>
<details><summary>ソースコード(クリックで展開)</summary>
<div>
```
n = int(input())
zero = n # とりあえずnで初期化
one = n # とりあえずnで初期化
# まずは0が代入された変数をつくる
for i in range(n):
# ループの1周目はiに0が代入されている
zero = i # 0が代入された変数をつくれた
one = zero # 1を代入する準備
break
# iが1のときもループが確実に実行されるよう,range関数にn+nを渡す
for i in range(n+n):
# ループ1周目はiとoneが0なのでこのif文はスルー
# ループ2周目はiが1なのでif文の中身が実行される
if one != i:
# oneに1をセットしてループを抜ける
one = i
break
# 3が代入された変数を作る
three = one
three += one
three += one
# 5が代入された変数を作る
five = three
five += one
five += one
# あとは普通のFizzBuzz
for i in range(one, n+one):
ans = ""
if i % three == zero:
ans += "Fizz"
if i % five == zero:
ans += "Buzz"
if ans == "":
ans = str(i)
print(ans)
```
</div>
</details>
### エレガントな解法(C/C++/Python)
<details><summary>解答方針(クリックで展開)</summary>
<div>
C言語の等価演算子`==`は,左側のオペランド(変数など)と右側のオペランドが等しければ整数値`1`を,そうでなければ整数値`0`を返します.例えば,`a == b`は`a`と`b`が等しいときに`1`, 等しくないときに`0`を返します.これを使えば,整数値`0`, `1`が代入された変数を作れます.
Pythonの場合は等価演算子による比較を実行すると`True`または`False`が返ってきますが,これを`int`型に変換すれば`1`, `0`になります.
あとは数字の`0`, `1`が代入された変数を使って整数値`3`, `5`が代入された変数を用意し,普通のFizzBuzzのように解きます.
</div>
</details>
<details><summary>C言語のソースコード(クリックで展開)</summary>
<div>
※ソースコードに不備があったので修正しました.
```
#include <stdio.h>
int main(void){
int zero, one, three, five, n, i;
scanf("%d", &n);
zero = (int)(n != n);// 同じ値を比較するので必ず0が返ってくる
one = (int)(n == n);// 同じ値を比較するので必ず1が返ってくる
// 3が代入された変数をつくる
three = one;
three += one;
three += one;
// 5が代入された変数をつくる
five = three;
five += one;
five += one;
// いつものFizzBuzz
for(i = one; i <= n; i += one){
if(i % three == zero && i % five == zero){
printf("%s\n", "FizzBuzz");
}else if(i % three == zero){
printf("%s\n", "Fizz");
}else if(i % five == zero){
printf("%s\n", "Buzz");
}else{
printf("%d\n", i);
}
}
return zero;
}
```
</div>
</details>
<details><summary>Pythonのソースコード(クリックで展開)</summary>
<div>
```
n = int(input())
zero = int(n != n) # 同じ値を比較するので必ずFalseが返ってくる int型に変換して0にする
one = int(n == n) # 同じ値を比較するので必ずTrueが返ってくる int型に変換して1にする
three = one + one + one # 3をつくる
five = three + one + one # 5を作る
# あとは普通のFizzBuzz
for i in range(one, n+one):
ans = ""
if i % three == zero:
ans += "Fizz"
if i % five == zero:
ans += "Buzz"
if ans == "":
ans = str(i)
print(ans)
```
</div>
</details>
## 環境依存な解法(非推奨)
この解法は,1バイト文字の文字コードがASCIIである場合ことを大前提としています.ASCII以外の文字コードを使用している環境では正しく動作しない可能性があるため,この解法は非推奨です.(競技プログラミングでは,1バイト文字の文字コードがASCIIであるという前提でプログラムを作成する場面もあります.)
[ASCIIコード表](http://www3.nit.ac.jp/~tamura/ex2/ascii.html)では,小文字の`a`から`z`に連続した番号が割り当てられています.C言語では文字も整数値として扱われるので,これを使って所望の整数値を表現します.
* 整数値`0`を表現したいときは`'a'-'a'`などとすればよいです.
* 整数値`1`を表現したいときは`'b'-'a'`などとすればよいです.
* 整数値`3`を表現したいときは`'d'-'a'`などとすればよいです.
今回は最大でも整数値`15`まで表現できればプログラムを組めます.英小文字は26個あるので,必要となる数値は上記のような英小文字同士の引き算ですべて表現できます.よって,答えとなるコードは次の通りです.
```
#include <stdio.h>
int main(void){
int n, i;
scanf("%d", &n);
// 'b'-'a' は整数値1
for(i = 'b'-'a'; i <= n; i++){
if(i % ('p'-'a') == 'a'-'a'){
// 'p'-'a' は整数値15
printf("%s\n", "FizzBuzz");
}else if(i % ('d'-'a') == 'a'-'a'){
// 'd'-'a' は整数値3
printf("%s\n", "Fizz");
}else if(i % ('f'-'a') == 'a'-'a'){
// 'f'-'a' は整数値5
printf("%s\n", "Buzz");
}else{
printf("%d\n", i);
}
}
return 'a'-'a';// 最後のreturn 0; も0を文字同士の引き算で表現
}
```
## 感想
数字を使わずにFizzBuzz問題を解くのは,ある種のパズルです.エレガントな解法が思いつくと大変うれしいです.腕力でゴリ押しする解法で無理やり通したときも,それはそれで達成感があります.
この記事を書くにあたって,中の人が1年次の頃に使っていた教科書を引っ張り出したり,言語仕様をググったりしました.普段は当たり前のように使っている演算子のことも,いざ記事に書こうとすると説明文が思いつかないものです(中の人の文章構成力が弱いだけ?).真面目に記事を書こうとすると,知っているつもりのことも改めて勉強することになりますね.
もうすぐお正月ですが,中の人は相変わらずの独り身です.気楽といえば気楽ですが,寂しくもあります.そろそろ正月に何を食べるか考えないといけませんね.…クリスマス?交際相手のいない中の人にそんなものはありません.