引用元:https://www.ioccc.org/1998/schnitzi.c
審査員・作者による説明:https://github.com/ioccc-src/winner/blob/main/1998/schnitzi.hint
動作
ソートをするC言語プログラムを出力する。
$ gcc -m32 -o schnitzi schnitzi.c
$ ./schnitzi 3 > sort.c
$ gcc -o sort sort.c
$ echo "2 3 1" | ./sort
1 2 3
解説
リポグラムがテーマになっている。
リポグラムとは文学界の用語で、特定の文字を使わないという制限のもとで文章を書くこと。
元のプログラムも、出力されたプログラムも、ある種のリポグラムになっているところが特徴。
出力されたプログラムから解説する。
これは、配列やループを一切使わないものになっている。
ソートアルゴリズムで配列もループも使わないのはちょっと驚くが、実際の出力を見ればすぐに納得できる。
$ ./schnitzi 3
main()
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
if (a<b)
if (b<c)
printf("%d %d %d ",a,b,c);
else if (a<c)
printf("%d %d %d ",a,c,b);
else
printf("%d %d %d ",c,a,b);
else
if (a<c)
printf("%d %d %d ",b,a,c);
else if (b<c)
printf("%d %d %d ",b,c,a);
else
printf("%d %d %d ",c,b,a);
}
このように、入力サイズnに対して2n個の分岐を書き下したプログラムを生成する。
さて、元のプログラムのすごいところは、条件分岐を一切使っていないこと。
もちろん単にif
文を使っていないという話ではなく、while
文も?:
も&&
も使っていない。
ついでに、関数の引数も返り値も使っていない。
基本的な構成は次の通り。
main
関数はO
という関数ポインタの入っている変数を繰り返し呼び出す。
呼び出された関数は、次に呼び出してほしい関数をO
にセットする。
そのうちO
にexit
関数が代入されるので正常終了する。
ただしこれでも、上記のような出力を出すためには条件分岐が必要である。
これを実現するのは次の4行。
void _j(){ O=_o; m=!m; h=h+m*(long)t; }
...
void _c(){ O=(e*)h; }
void n(){ O=_j; t=w; _o=_c; }
void p(){ O=_j; h=0; t=l; _o=n; }
p
を呼び出すと、h
を0に初期化し、その後_j
→n
→_j
→_c
という順で呼び出され、最後にh
に入っている関数が呼び出されることになる。
このときにh
に何が入るかを考える。
m
が偽(ゼロ)だったとすると、最初の_j
の呼び出しでm
は1になり、h
はl
になる。2回目の_j
の呼び出しではm
が0になるので、h
の値は変わらない。よって、最終的にl
が呼び出される。
m
が真(非ゼロ)だったとすると、最初の_j
の呼び出しでm
が0になり、h
は0のまま。2回目の_j
の呼び出しではm
が1になり、h
にはw
の値が入る。よって、最終的にw
が呼び出される。
ということで、m
が0かどうかによって、l
とw
を呼び分けるギミックになっている。
ポインタをint
として扱うので、-m32
が必要。
int
の変数宣言をlong
にし、main
関数の第2引数もlong
に修正するのでもよい。