はじめに
codegolf ShortCodingと聞くと、「何してるかわかんない」「見にくい」「意味ない」等マイナスなイメージが付きがちですが、そんなことはありません!
確かに見にくいのは事実。通常であれば誰もが理解出来るコードを書くべきです。(自分も普段はそう書いてます。)
しかしコードを短くすることは簡単ではありません。ただ見にくくすれば良いわけではありません。
データ構造・アルゴリズム・処理の観点から探求し、コードを短くするためにありとあらゆる知識を使います。
コードを短くするには想像以上の知識、そして発想力が求められるのです。
日々の生活等に役に立つかと言われたら微妙です、ですが考え方や言語への理解、そして熱き精神が必ずあなたを成長させます。
今までの先入観を捨て是非楽しんでいってください。
あくまでも遊びの一つです、実際のコードでは見やすく分かりやすく書いてください。
短縮する元のコード
Wikipediaを参照してください。
https://ja.wikipedia.org/wiki/Fizz_Buzz
今回はこのWikipediaのサンプルコードを短くしていきます。
#include <stdio.h>
int main(void) {
int i;
for (i = 1; i <= 100; i++) {
if (i % 3 == 0 && i % 5 == 0) {
printf("FizzBuzz\n");
} else if (i % 3 == 0) {
printf("Fizz\n");
} else if (i % 5 == 0) {
printf("Buzz\n");
} else {
printf("%d\n", i);
}
}
return 0;
}
最終的にはこうなります。順を追って短くして行きましょう!
main(i){for(;i<101;puts(i++%5?"":"Buzz"))printf(i%3?i%5?"%d":0:"Fizz",i);}
実際に短くしてみよう
codegolfをするにあたりErrorを大量に吐きますが、動けば問題無いので無視してOKです。
短くするためならセキュリティ面も気にしなくていいのでgets
等の関数も積極的に使っていきましょう。
バージョンはgcc version 6.3.0です。
gccを使おう
c言語でcodegolfをするならgccがおすすめです。
gccは破茶目茶なコードを書いても無理やりバイナリを生成してくれるので、例外はありますが基本的に1番短くなります。
基本的な削りから
それでは削れるところから削っていきます。
まず簡単なところから、
スペースを詰める
改行は後から削った方が理解しやすいので、いつも後回しにしています。
return 0;
を削ります。C99は
main
関数でreturn
文を省略すると自動的に0
を返すので必要ありません。
#include <stdio.h>
を削ります。
#include
を省略するとErrorを吐きますが動きます。おまけに型の判定がゆるくなります。
int main(void)
を削ります。
main
というシンボルさえ定義されていれば動きます。
なのでmain()
で動きます。
int i
の場所を工夫するgccではグローバル変数の型を省略するとint型になります。
さらにグローバル変数の初期値を省略すると0
になります。
省略出来る場所を消すだけでこんなに短くなります
i;main(){
for(i=1;i<=100;i++){
if(i%3==0&&i%5==0){
printf("FizzBuzz\n");
}else if(i%3==0){
printf("Fizz\n");
}else if(i%5==0){
printf("Buzz\n");
}else{
printf("%d\n",i);
}
}
}
ちょっと頭をひねってみる
3
かつ5
で割れる時、実質15
で割れる時なのでそこをまとめます。
if(i%3==0&&i%5==0){
if(i%15==0){
条件式を変える
i<=100
をi<101
にすると1byte省略されます
インクリメントと条件式をまとめる
i<101;i++
を++i<101
にするとまずインクリメントされてから条件判断します。
ちなみにi++<101
にすると条件判断した後にインクリメントをします。この使い分けが出来ると1byte減ったりするので完全に理解しましょう。
条件判断の前にインクリメントするので初期値は0
になります、gccでグローバル変数の初期値を省略すると0
になるので、初期値設定を省略出来ます。
10byte以上短くなる
i;main(){
for(;++i<101;){
if(i%15==0){
printf("FizzBuzz\n");
}else if(i%3==0){
printf("Fizz\n");
}else if(i%5==0){
printf("Buzz\n");
}else{
printf("%d\n",i);
}
}
}
codegolferは
for
を使う
for
文の中を書かなくても動きます。むしろ別の式をぶっこめるのでwhile
は使っては行けません、for
を使いましょう。
printf("Hello, World");
for(;i<10;){
printf("Hello, World");
}
for(printf("Hello, World");i<10;printf("Hello, World"));
for
とwhile
のbyte数も同じです。byte数が一緒while() for(;;)
無限ループならforのが短いwhile(1) for(;;)
ifは使わない
codegolfする際にif
文は使いません。三項演算子か論理演算子の二択です。
この2種類を場合によって使い分けましょう。
三項演算子とは
条件式 ? true文 : false文;
なのでif
文と対して変わりません、ただし三項演算子は式だということを忘れないでください。
三項演算子は式なので結果を代入したり、printf
の中にぶっこんだり出来ます。そこが最大の強みです。
ちなみに三項演算子のtrue文
のところは()
が入りません、覚えておくと役に立ちます。
全て同じ結果if(i==5){puts("true");}else{puts("false");} i==5?puts("true"):puts("false"); puts(i==5?"true":"false");
論理演算子の仕様
論理演算子は左を判断したあとに右を判断します。
AND演算子は左側がfalse
の時、右側を評価しません。
OR演算子は左側がtrue
の時、右側を評価しません。
なのでif
文と同じ使い方が出来ます。
イコールの時if(i==5){puts("true");} i==5&&puts("true");
ノットイコールの時if(i!=5){puts("false");} i==5||puts("false");
遂に100byteを切る
今回はif
文を三項演算子に変更します。
i;main(){
for(;++i<101;){
i%15==0?printf("FizzBuzz\n")
:i%3==0?printf("Fizz\n")
:i%5==0?printf("Buzz\n")
:printf("%d\n",i);
}
}
さらにprintf
の中に式をぶっこみます。
for
文は一行ならブレースが要らないのでそこも省略し、改行を消します。
i;main(){
for(;++i<101;)
printf(i%15==0?"FizzBuzz\n":i%3==0?"Fizz\n":i%5==0?"Buzz\n":"%d\n",i);
}
i;main(){for(;++i<101;)printf(i%15==0?"FizzBuzz\n":i%3==0?"Fizz\n":i%5==0?"Buzz\n":"%d\n",i);}
100byteを切りましたね!まだまだ詰めていきます。
ド・モルガンの法則、true
false
について
ド・モルガンの法則
ド・モルガンの法則!(P || Q) == !P && !Q !(P && Q) == !P || !Q
ド・モルガンの法則ってどう使うの?と思ったあなた。
全て同じ!(P != 0 && Q != 0) !(!(P == 0 || Q == 0)) P == 0 || Q == 0
ド・モルガンの法則を使用するとコードがスッキリするので普段使いも出来ます!
条件式の仕様について
条件式は
0
ならfalse
、!0
ならtrue
。
P!=5
とは、P
が5
以外ならtrue
、要するにP
が5
ならfalse
。
P-5
は0
、0
はfalse
、要するにP!=5
とP-5
が同じ。
全て同じP==5&&puts("") P!=5||puts("") P-5||puts("")
全て同じP==0?true:false P!=0?false:true P?false:true
このようにド・モルガンの法則を利用したり、条件式の仕様を理解する事でコードが短くなります。
今回のコードでは
printf(i%15==0?"FizzBuzz\n":i%3==0?"Fizz\n":i%5==0?"Buzz\n":"%d\n",i);
中の三項演算子でド・モルガンの法則をしてみます。
一旦、理解し難いのでif
文に戻します。
if (i % 15 == 0) {
printf("FizzBuzz\n");
} else if (i % 3 == 0) {
printf("Fizz\n");
} else if (i % 5 == 0) {
printf("Buzz\n");
} else {
printf("%d\n", i);
}
ド・モルガンの法則でnotにしてみます。
if (i % 15 != 0) {
if (i % 3 != 0) {
if (i % 5 != 0) {
printf("%d\n", i);
} else {
printf("Buzz\n");
}
} else {
printf("Fizz\n");
}
} else {
printf("FizzBuzz\n");
}
!=0
は0
じゃない場合はtrue
、なので!=0
を消しても動きます。
if (i % 15) {
if (i % 3) {
if (i % 5) {
printf("%d\n", i);
} else {
printf("Buzz\n");
}
} else {
printf("Fizz\n");
}
} else {
printf("FizzBuzz\n");
}
三項演算子に戻します。
i%15?i%3?i%5:printf("%d\n",i):printf("Buzz\n"):printf("Fizz\n"):printf("FizzBuzz\n")
>
>`printf`に入れ直します。
>
>``` c
>printf(i%15?i%3?i%5?"%d\n":"Buzz\n":"Fizz\n":"FizzBuzz\n",i);
>```
### 更に減ったコード
85byteまで減りました。
``` c:85byte
i;main(){for(;++i<101;)printf(i%15?i%3?i%5?"%d\n":"Buzz\n":"Fizz\n":"FizzBuzz\n",i);}
1byte減らすのに頭を使う
ここから先はとても分かりにくく、難しくです。
むしろ、ここからが本番です。頑張りましょう。
15
ではなくす最初の方に
3
かつ5
で割れる時、実質15
で割れる時と言いました。
確かに&&
を使うなら15
にしたほうが短いです、ですが下のようにするとどうでしょう?
3
で割れて5
で割れるFizzBuzz
。
3
で割れて5
で割れないFizz
。
3
で割れずに5
で割れるBuzz
。
3
で割れずに5
で割れない数字。こうすると
15
も&&
使ってないので1byte短く出来ます!
アルゴリズムを作り直す
同じく理解し難いので
if
文に戻します。
if (i % 15) {
if (i % 3) {
if (i % 5) {
printf("%d\n", i);
} else {
printf("Buzz\n");
}
} else {
printf("Fizz\n");
}
} else {
printf("FizzBuzz\n");
}
並び替えます。
if (i % 3) {
if (i % 5) {
printf("%d\n", i);
} else {
printf("Buzz\n");
}
} else {
if (i % 5) {
printf("Fizz\n");
} else {
printf("FizzBuzz\n");
}
}
三項演算子に戻し、
printf
に入れ直します。printf(i%3?i%5?"%d\n":"Buzz\n":i%5?"Fizz\n":"FizzBuzz\n",i);
1byte減りました
i;main(){for(;++i<101;)printf(i%3?i%5?"%d\n":"Buzz\n":i%5?"Fizz\n":"FizzBuzz\n",i);}
別解
改行するのに通常
\n
を使いますが、puts
は自動的に改行をしてくれるので、puts
で何も出力しなければただ改行します。
printf
をしたあとにputs
で何も出力しなければ、printf
のなかでわざわざ\n
を4回も書かなくて済みます。
今回はbyte数が同じですが、場合によっては短くなります。84bytei;main(){for(;++i<101;puts(""))printf(i%3?i%5?"%d":"Buzz":i%5?"Fizz":"FizzBuzz",i);}
FizzBuzzを省略したい
printf(i%3?i%5?"%d\n":"Buzz\n":i%5?"Fizz\n":"FizzBuzz\n",i);
Fizz
とBuzz
が2回書いてあるのを省略したくありませんか?
頑張って省略してみます。
2回出力にしてFizzBuzzを分割する
if
文に戻します。
if (i % 3) {
if (i % 5) {
printf("%d\n", i);
} else {
printf("Buzz\n");
}
} else { // ← ここ
if (i % 5) {
printf("Fizz\n");
} else {
printf("FizzBuzz\n");
}
}
ここの
else
で分割出来そうじゃないですか?
分割してみます。
if (i % 3) {
if (i % 5) {
printf("%d", i);
} else {
printf("");
}
} else {
printf("Fizz");
}
>
if (i % 5) {
puts("");
} else {
puts("Buzz");
}
1回目の
if
はFizz
の方の処理をしていて、2回目のif
はBuzz
の方の処理をしています。
詳しく説明します。
各パターンでの流れです。
FizzBuzz
の場合1回目の
if
では、3
で割れるのでFizz
を出力します。
2回目のif
では、5
で割れるのでBuzz
を出力します。
Fizz
の場合1回目の
if
では、3
で割れるのでFizz
を出力します。
2回目のif
では、5
で割れないのでなにも出力しません。
puts
で何も出力しないので改行だけされます。
Buzz
の場合1回目の
if
では、3
で割れなくて5
で割れるのでなにも出力しません。
printf
なので改行しません。
2回目のif
では、5
で割れるのでBuzz
を出力します。数字の場合
1回目の
if
では、3
で割れなくて5
で割れないので数字を出力します。
2回目のif
では、5
で割れないのでなにも出力しません。何も出力しないのを挟むことで、2回に出力を分割出来ました。
コードに戻してみる
まずは三項演算子に戻します。
i%3?i%5?printf("%d",i):printf(""):printf("Fizz"); i%5?puts(""):puts("Buzz");
次に
printf
puts
に入れ直します。printf(i%3?i%5?"%d":"":"Fizz",i); puts(i%5?"":"Buzz");
ほぼほぼ完成系
for(;;puts(i%5?"":"Buzz"))printf(i%3?i%5?"%d":"":"Fizz",i);
for
の間に入れることで;
を1つ省略しています。
i;main(){for(;++i<101;puts(i%5?"":"Buzz"))printf(i%3?i%5?"%d":"":"Fizz",i);}
極限まで減らす
もう減らせる場所がなさそうですが実はまだあります。
printf(0);
printf(0);
とやると何も表示されません。
ですが何も出力していないから何も表示されていないのではなく、エラーが起きています。
実はprintf
には戻り値があります。戻り値は出力された文字数で、エラーの場合は負の値が返されます。
int i = printf("abc\n");
printf("%d\n", i); // 4
>
int j = printf(0);
printf("%d\n", j); // -1
>
int k = printf(0, "abc\n");
printf("%d\n", k); // -1
なので今回は
""
を0
に変えます、これで1byte。Beforeprintf(i%3?i%5?"%d":"":"Fizz",i);
Afterprintf(i%3?i%5?"%d":0:"Fizz",i);
i
のインクリメントグローバル変数の型を省略するとint型になり、初期値を省略すると
0
になるというのは、先程説明しました。
実は初期値を1
にする方法もあります。main(i){printf("%d",i);} // 1
main
の第1引数はargc
です。argc
はコマンドラインに記述されたパラメータの数を格納します。
プログラム自体も数えられるので、引数を渡さない場合1
になります。
初期値が1
で行けるなら下のほうが1byte短いです。初期値が0i;main(){}
初期値が1main(i){}
引数の数を増やすと
argc
の数が増えます。main(i){printf("%d",i);}
今までは
i
の初期値が0
で、インクリメントしたあとに条件判断でした、なのでi
の初期値を1
にして、条件判断した後にインクリメントすれば、1byte短く出来ます。
++i<101
をi++<101
にしてもprintf
の中身でi
を使用しているので駄目です、全ての処理をした後にインクリメントしなければなりません。
printf
とputs
を分割したのでputs
の条件判断の後にインクリメントすれば、全ての処理をした後にインクリメント出来ます!Beforei;main(){for(;++i<101;puts(i%5?"":"Buzz"))printf(i%3?i%5?"%d":"":"Fizz",i);}
Aftermain(i){for(;i<101;puts(i++%5?"":"Buzz"))printf(i%3?i%5?"%d":"":"Fizz",i);}
完成
お疲れ様です。
長い戦いがこれで終了です。
今までを振り返りながら見ると完成したコードは自明です。
main(i){for(;i<101;puts(i++%5?"":"Buzz"))printf(i%3?i%5?"%d":0:"Fizz",i);}
最少は73byteなんですが自分の環境では自分の環境だと上手く動きませんでした。(gcc version 6.3.0)
73bytemain(i){for(;i<101;puts("Buzz"-i*i++%5))printf(i%3?i%5?"%d":0:"Fizz",i);}
最後に
1つ1つ丁寧に説明したらとても長くなってしまいました。
これをきっかけに是非codegolfに挑戦してみて下さい。
色んなFizzBuzzのcodegolfが見れるのでこちらの記事も是非見て下さい。
FizzBuzzを1byteで実装する