前書き
本記事は、Qiitaのesolang Advent Calendar 2024 の、12日目の記事です。
世の中にはできる限りプログラムの文字数を少なくして競う競技、通称「Code Golf」と呼ばれるものが存在しています。
まあそれをやっていきます。
ちなみに今回は基本的にバイト数を減らす方針です。
(2024/12/13 15:50分追記)
最小文字数を更新しましたので、追記に書きました。
言語紹介
今回使用する言語は2025年度の共通テストから行われる「情報I」のテスト内で実際に使用される言語です。
(2024/12/17 12:30追記)2024年度までの「情報関係基礎」のテスト内で使用されていた言語でした。
訂正してお詫び申し上げます。(@nodai2h_ITCさん、ありがとうございます。)
言語名は「DNCL」といいます。(Daigaku Nyushi Center Languageだと言われているそう)
そして意外と言語仕様がしっかりしているため、実際に実行することが可能だそうです。
今回はDNCL学習環境である「どんくり」を用いてCode Golfをします。
この言語は大学入試をするためにとてつもなく魔改造作られたされた言語であり、コードの大部分が日本語で構成されている(特に構文が)、若干Pythonよりの言語です。
しかし、「なでしこになることができなかった」、「別にPythonでもいいだろ」、「読みにくい」、「言語仕様がひどい」、「Esolang」などと散々な言われ方をしている可哀想な言語です。~
ということで、一部の人から猛烈に批判が飛んでくるようなかわいそうな言語でCode Golfをしてみようと思います!
コード
してみようとは言ってもこの記事を書いている時点ですでにコードは書き切っているので先にコードをお見せします
ドドン(セルフ)
i←0i≦99の間、n(x,y)はa←{y}a[i%x+1]または""を返すを実行する i←i+1n(3,"Fizz")+n(5,"Buzz")またはiを表示するを繰り返す
(86文字、118バイト)
はい。
はい?
はい。
見てもらえばわかる通り、見てもわからないです。
ということで、どのように縮めていったか、またどのようなところで苦戦したかを見てみましょう。
DNCLのCode Golfテクニック!(無需要)
(改行を減らすみたいな大体どの言語でも言えるようなことは省きます)
DNCLは日本語を主体としている言語なので、ひらがなや漢字が多くなればなるほどバイト数が増えてしまいます。
なのでできる限り数式で書くということを念頭に置きながら書きます。
繰り返し編
いろんな言語でCode Golfをやったことがある人ならわかると思いますが、
for文って長いんですよ。
しかもこのDNCL、PythonのRangeみたいな挙動するので特に文字数とられるんです。
まあとりあえず見てみましょう。
iを0から10まで1ずつ増やしながら、
iを表示する
を繰り返す
長いですね。
ということでほかのループも試してみましょう。
i←0
i<10の間、
iを表示する
i←i+1
を繰り返す
i←0
ここから10回、
iを表示する
i←i+1
を繰り返す
f(i)は
iを表示する
もしi!=10ならばf(i+1)を実行する
を実行する
f(0)
どうやらwhileが一番短そうですね。
条件分岐編
まあこんな記事読んでる時点で知っている方がほとんどでしょうが、if以外にも条件分岐する方法はあります。
もし1ならば
1を表示する
を実行し、そうでなければ
2を表示する
を実行する
割と長いですね。
他を見てみましょう。
a←{1,2}
a[0]を表示する
相当スッキリしましたね。
ただこの言語、「一度代入しないといけない」「Booleanの型変換が出来ない」「関数内の引数にBooleanを代入できない」と言うEsolangをEsolangたらしめる仕様があるので使い所には注意ですね。
関数内で1を返す
とかが使えるなら
もし1ならば
1を返す
を実行する
2を返す
1の間、
1を返す
を繰り返す
2を返す
この言語本当にwhileが強いですね
あとは論理演算子も用意されてます。
1または2を返す
解説
i←0
i≦99の間、
n(x,y)は
a←{y}
a[i%x+1]または""を返す
を実行する
i←i+1
n(3,"Fizz")+n(5,"Buzz")またはiを表示する
を繰り返す
割と読みやすいですね!
上のパートで言った通り、反復処理はwhileが強いのでwhileで書いてます。
で、見たら分かる通り、何故か関数化されてますね?
これなにがあったかというと、、、
FizzBuzzの分岐は短くするためにできる限り数式で書きたい
↓
まあ{i,"Fizz","Buzz","FizzBuzz"}[i%3+i%5*2+1]
だと思ってた
↓
{1}[0]
の用に直接くっつけられないことに気づく
↓
True
を数字に変換することが出来ないことに気づく
↓
余りが0の時だけの分岐{1}[0]または2
をFizzとBuzzそれぞれに使う
↓
処理が被ってるのをまとめる
みたいな経緯です。
(ちなみにi%x+1
となっているのは配列が1スタートだからです。)
もう主要な部分殆ど終わってしまったんですが
n(3,"Fizz")+n(5,"Buzz")またはiを表示する
として、消してもエラーを吐かない改行を取っ払ったら終わりです!
i←0i≦99の間、
a←{y}a[i%x+1]
i←i+1n(3,"Fizz")
どうやら代入の右辺には1つの要素しかこないためスペースがなくても動くようです。
を実行する i←i+1
しかし構文末尾のを実行する
のあとにはスペースや改行を挟まないと動きません。
なんで?
追記
(2024/12/13 15:50追記)
i←0i≦99の間、i←i+1a←{"FizzBuzz","Buzz","Fizz",i}a[i*i%3+i*i%5%3*2+1]を表示するを繰り返す
(75文字、92バイト)
(@angel_p_57さん、ありがとうございました!)
解説
i←0
i≦99の間、
i←i+1
a←{"FizzBuzz","Buzz","Fizz",i}
a[i*i%3+i*i%5%3*2+1]を表示する
を繰り返す
大分スッキリしましたね。
もはや分からない所が数式だけだと思います。
i*i%3
を表にすると
i = | 1 | 2 | 3 | ... |
---|---|---|---|---|
i2 = | 1 | 4 | 9 | ... |
i2 % 3 = | 1 | 1 | 0 | ... |
となり、i%3==0
の時だけ0
になる関数となります。
また、i*i%5%3*2
を表にすると
i = | 1 | 2 | 3 | 4 | 5 | ... |
---|---|---|---|---|---|---|
i2 = | 1 | 4 | 9 | 16 | 25 | ... |
i2 % 5 = | 1 | 4 | 4 | 1 | 0 | ... |
i2 % 5 % 3 = | 1 | 1 | 1 | 1 | 0 | ... |
i2 % 5 % 3 * 2 = | 2 | 2 | 2 | 2 | 0 | ... |
となり、i%5==0
の時だけ0
になる関数となります。
これにより、
i*i%3+i*i%5%3*2+1
この数式は、
3
でも5
でも割り切れる時4
が、
3
で割り切れて5
で割り切れない時3
が、
5
で割り切れて3
で割り切れない時2
が、
15
で割り切れる時1
が
返ってくるので、配列の参照している場所にきれいに対応します。
あとがき
ここまでお読みいただきありがとうございました!!!
1時間程度で書いたコードなのでもしかしたらまだ省略できる箇所があるかもしれません、、
何かあればコメントにお願いします!
dev