2日目は、本州中央部を舞台にTSGチームと外部ゲストのコードゴルフ強豪プレイヤーが争う「都道府県陣取り合戦」です! ゲストとしてこたつがめ(@kotatsugame_t)さんと%20(@henkoudekimasu)さんをお招きします! pic.twitter.com/QGGWt59NGR
— 東大コンピュータサークルTSG (@tsg_ut) 2020年9月19日
こういう大会があることは結構昔から知っていて、五月祭に足を運んでは実況を見ていたのですが、今年はなんと外部プレイヤーとして参加するチャンスがあるとのこと!意気揚々と立候補して、無事外部チームの1人として参加できることになりました。
追記:外部チームとして一緒に参加したx20さんの参加記が公開されました!リンクはこちらです。
また、対戦したTSGチームのうらさんの参加記も公開されました!リンクはこちらです。
5月にもコードゴルフ大会があって、そちらは東大と京大が合同で開催した、かなり大きな大会でした。僕以外の外部の人も数名参加されていて、たとえばanarchy golf
の管理人であるshinh
さんもいらっしゃいました。この大会のWriteupはここです↓
第6回東大京大コードゴルフ大会 Writeup · hakatashi/esolang-battle Wiki · GitHub
今回は特にそういった場が設けられているわけではないので、自分が書いたコードについてこのブログ記事をWriteup代わりにします。
問題概要
それ以前のどの数値よりも真に小さいか判定せよ。
入力
2桁の数値が50個、改行区切りで与えられる。入力の末尾には改行が付与される。
出力
与えられた数値それぞれについて、それ以前に与えられた数値のいずれよりも真に小さいとき1、そうでないとき0を出力せよ。
※ちなみに、出力中の空白文字(\s
)はすべて無視されます。
制約
- 同じ数値は2度以上出現しない。
- 入力の数値は10以上99以下である。
- 初日については1を出力すること。
C言語(55B)
main(a,p){for(;~scanf("%d",&a);)puts(p>a?p=a,"1":"0");}
初めに手を付けた言語です。最初問題を読み間違えていて、大きいかどうか判定して2回WAを出してしまいました。修正してACし、さらに3度縮めたのが開始6分半のことで、そのあとは誰にも取られませんでした。かなりシンプルなのでこれ以上縮まないと思っています。
強いてテクニックを挙げるなら、p
の初期値を設定する代わりに、main
関数の第2引数が十分大きいのを期待していることでしょうか。あとは親の顔より見たコード、といった感じに手癖で書けました。
Go言語(87B)
package main import."fmt" func main(){p:=100;for{a:=0;Scan(&a);if p>a{p=a};Print(p/a)}}
上にリンクを貼った、以前の大会のWriteupを見て書きました。一応最初の提出から25B
縮んでいます。まず出力するのにif
文とPrint
を2回書くのは明らかに長いので、そこを何とかします。p/a
すれば1
と0
が得られるので、場合分けがなくなってよさそうです。書いてる最中はGo言語の割り算が整数に切り捨てるか知らなかったのでちょっとドキドキしました。
これは副次的な効果をもたらします。a
の初期値として0
を入れておけば、Scan
に失敗したときにそのままa=0
となってp/a
がゼロ除算で落ちてくれます。こういう終わり方でもAC判定を得ることができるため、50回のループから無限ループに書き換えることができました。
周りにTSGチームの陣地がないので、一切脅かされませんでした。例えばp:=99
にするだけでも(1/90
の確率でWAするようになりますが)1B
縮みます。Go言語はほとんど使用したことがないので、探せばまだ少し縮むと思います。
Python3(49B→47B)
x=':' while t:=input(): if u:=x>t:x=t print(+u)
これはTSGチームに縮められてしまいました。ループ内部をx=min(x,t);print(1-(x<t))
とすると48B
のようです。if
のせいでインデントが入っているので縮む余地はありそうに見えていたのですが……。
配信アーカイブを見ていて気付きましたが、これで47B
になります。
x=[] for t in open(0):x+=t,;print(+(min(x)==t))
while t:=input()
がfor t in open(0)
になっているのは、同じ長さ(!)なので更新点ではありません。Python
には配列のmin
があるので、以前の最短を特別に保持する必要がなかったようです。この発想は大会中、一切出てきませんでした。
追記
x,r=': ' while t:=input(r): if r:=+(x>t):x=t
しーあるさんの改善により、45B
になりました。
x,r=': '
— しーある (@cs_c_r_5) 2020年9月21日
while t:=input(r):r=+(x>t);x=min(x,t)
みたいなのってあり?( 1001みたいな出力でいいなら)
GolfScript(27B→16B)
~].99\{[\]$~;.}%\;]zip{~=}%
~]
は入力全体を数値の配列にするイディオムです。まず入力が1つの文字列としてスタックに入っているので、~
でeval
して数値に分解し、]
で配列にまとめます。[
と対応していない]
は、公式のドキュメントにも載っている由緒正しい使用方法です。
次に、その配列をdup
して、cummin
を計算します。苦労しました。map
で実装することにしたのですが、以前の最大値を変数に持つ必要があるように見えます。
map
の仕様がよくわからなかったので、インタプリタの実装を確認しました。スタックサイズを記録したあとに要素をpush
してブロックを実行し、増えた分だけ取り出して新しい配列にしているみたいです。つまり以前の最大値をスタックに残したままやりくりすることができるはずなのですが、なかなかうまくいかず苦労しました。しかしハマっていたのは別のところだったらしく、結局かなりシンプルなコードになりました。
{[\]$~;.}
が実行するブロックです。先頭2要素を配列にまとめ、sort
してからdump
し、大きいほうの要素を削除して小さいほうの要素をdup
します。このdup
された要素のうち、片方は新しい配列に入れられて、もう片方はスタックに取り残され、次のループに備えます。
cummin
する前とした後の配列をzip
したあと、それぞれのペアについて値が等しいかどうかをスタックに積んでいきます。{~=}
です。コードが終了するとスタックの中身がすべて出力されるので、明示的に出力を書く必要はないです。
さすがにこんなコードが短い訳がない。ということで書き直しました。16B
です。
~]1\{.[@]$~<\}*;
美しいですね。map
でもeach
でもなくfold
を使っているのがポイントで、これで仮の最小値として99
を置く必要がなくなりました。代わりに、最初の出力として1
を用意しています。
fold
するブロックでは、これまでの最小値をa
、今見ている要素をb
としてa b
の状態で実行が開始されます。dup
してa b b
、[@]
するとこの3つが配列にまとめられます。sort
してdump
するのは同じで、a<b
ならa b b
、a>b
ならb b a
になります。後ろ2つの大小比較で0 or 1
を作り、先頭の要素は新しい最小値になります。この2つをswap
して次のループに備えます。
最後に計算してきた最小値を捨てれば、出力だけがスタックに残り、自動的に出力されます。
Crystal(38B)
x=":" while a=gets p x>a ?(x=a;1):0end
GolfScript
を取ったことによって提出できるようになった言語です。これより1B
長いものが書きあがって提出しようとしたところ、ほんの1分ほど前にx20さんが全く同一のコードを提出していました。コミュニケーションをとっていなかったため、お互い同じ言語を縮めていることに気づけませんでした。試しに0
とend
の間の改行をなくしてみたところ動いたので、そこで-1B
しました。
Crystal
においてgets
はString? = String or Nil
を返します。本来であればこれをString
と比較するとCEになるのですが、while
の条件式に書くことでNil
ではないという推論が働き、String
型になってくれます。
あとはまあ、Ruby
とほとんど一緒です。Char
とString
が別物のようで、x=?:
と書くとCEでした。
><>(45B)
aa*v ; i~ >:i:0(?^68*-a*i68*-+:@(:1$-n?~
最後に焦りながら取った言語です。書きあがったのが終了3分前でしたが、提出場所を間違えているのに1分間気づかず、冷や汗を流しました。ほとんどゴルフできていません。例えば文字コードから数値に直す部分など、48
を2回引くよりも48*10+48
を1回引いたほうがどう考えても短くなります。というかそもそも、比較するだけなので定数を引く必要すらありません。仮の最小値を適切に大きくしておけばよいです。
最初は1行目に処理を書いて2行目で先頭に戻るコードを書いていたのですが、仮の最小値を持つ部分との兼ね合いで処理が2行目に来ました。すると2行目だけでループできることに気づき、先頭に戻る処理を省くことができました。これは数少ないゴルフしたポイントといって良いでしょう。
頭がGolfScript
になっていたので、コマンドの違いに苦労しました。pop
は;
ではなく~
、dup
は.
ではなく:
、swap
は\
ではなく$
、@
でrotate
する方向が逆……。
まとめ
時間中に触れたのは全部、以前に書いたことがある言語でした。意気込みとして「いろんな言語を触る」と言ってはみたものの、時間が圧倒的に足りません……。
Ruby
とPython3
が取られたのは、結構ショッキングでした。Ruby
の三項演算子はただでさえ正しく解釈されるために非自明な空白の入れ方をしないといけない上、_1
が絡むといろいろ試すべきことが増えます。今回の短縮はx>_1 ? T:F
と書くと空白が2つ必要になる一方、演算子を逆にして_1<x ?T:F
と書くと1つで済むというものでした。
問題はかなり面白かったです。75分という短い時間でそこそこ盤面を広げるにはちょうどいい難易度でした。盤面を日本地図にするのも、提案されたときは結構度肝を抜かれましたが、何度かアンケートでバランス調整されたこともあってか、特に引っかかることなく楽しめました。(これは外部チームの手を出せるマスが広かったので不自由しなかっただけかもしれませんが……。)
時間がないとは言いましたが、これ以上長くすると配信に影響が出そうなので、触れない言語があったのはしょうがない話です。事前説明で聞いた話では、Unlambda
でもACできるアルゴリズムを開発してはいたそうです。すごすぎます。
大満足の大会でした。共に参加したx20さん、対戦してくださったうらさん、博多市さん、またこのような場を設けてくださったTSGの皆さん、本当にありがとうございました!