From 2b102673ceb57081891e153b896aba5568dbc1f1 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 27 Nov 2025 02:12:38 +0900 Subject: feat(nuldoc/blog): New post /posts/2025-11-27/anybatross-writeup/ --- .../content/posts/2025-11-27/anybatross-writeup.dj | 210 +++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj (limited to 'services/nuldoc/content/posts') diff --git a/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj b/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj new file mode 100644 index 0000000..d67d63d --- /dev/null +++ b/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj @@ -0,0 +1,210 @@ +--- +[article] +uuid = "2ed2d400-b3f6-48bc-af27-19d9042875a0" +title = "カヤックさん開催のコードゴルフコンテスト Anybatross に参加して優勝した" +description = "YAPC::Fukuoka 2025 に際してカヤックさんが開催されていたコードゴルフコンテスト、Anybatross に参加して優勝した。" +tags = [ + "perl", + "ruby", + "yapc", +] + +[[article.revisions]] +date = "2025-11-27" +remark = "公開" +--- +{#intro} +# はじめに + +[YAPC::Fukuoka 2025](https://yapcjapan.org/2025fukuoka/) に際してカヤックさんが開催されていたコードゴルフコンテスト、[Anybatross](https://perlbatross.kayac.com/contest/2025fukuoka) に参加し、優勝した。 +この記事では自分の回答について解説する。 + +なお今回のシステムでは、現在の自分のスコア以下のコードを開催中も閲覧できる仕様だった。Hole 1 では特に使わなかったが、Hole 2 では途中他の方のコードをベースに進めた箇所がある (詳しくは後述)。 + +このコンテストは何度か開催されており、[前々回](https://perlbatross.kayac.com/contest/2024hiroshima) に参加したときは総合 2 位だった (前回開催は不参加)。 + +{#hole-1} +# Hole 1 + +{#answer} +## 回答 (45 byte) + +```perl +print$a+=$\=y/8B/0/+y/0469ADO-R//.$/,","for<> +``` + +Hole 1 については同一言語・同一スコアの回答が複数あるので詳細は省略する。 + + +{#hole-2} +# Hole 2 + +こちらは縮めていった過程も記載する。 + +{#answer-a} +## 回答 A (107 byte) + +最終スコアを見ると 4 位タイ (107 byte) が多く、3 位以上の回答と明確にアルゴリズムの差があるのでここから解説をスタートしようと思う。 + +```ruby +s=gets +?A.upto(?Z){(b,),m=s.scan(/(?=(.\B.))/).tally.max_by{_2} +m>1&&(s.gsub!b,it;$*< b = "la", m = 3 +``` + +置換テーブルのデータは `$*` へと追加しているが、これは Ruby の特殊変数で、本来は `Object::ARGV` を指す。ここでは単に最初から空配列で初期化されている便利な入れ物として用いている。 + +その他、`?A` や `String#*`、`it`、numbered parameters などの細かいテクニックについては、「Ruby コードゴルフ」で調べるか、最近の Ruby のリリースノートを読むと見つかるはず。 + +{#answer-b} +## 回答 B (107 byte) + +回答 A をぐっと睨むと、`m>1&&(...)` の括弧を削りたくなる。しかしそれには `m>1&&` がどうしても邪魔になる。というわけで終了条件を工夫することでなんとか `m` を排除できないかを考えた。それがこちら。 + +```ruby +s=gets +?A.upto(?Z){(b,),=(?_+s).scan(/(?=(.\B.))/).tally.max_by{_2} +$*< 参加者のみなさんの最短解はshebangを書いてPerlのオプションを設定していい感じにやるものでしたが、 +> (中略) +> 今回はチート抑止みたいなところの意図でperlコマンドを実行する方式になったので、ちゃんとshebangを書けば効くようになっていたのでした。 + +Shebang が使えるのなら、Ruby にも Perl に由来するオプションがいくつかあるので、似たような手段で短縮できるのではないか? + +`ruby` で `-p` を付けると、以下のようなコードを書いたかのように動作する。 + +```ruby +while gets + ... # 記載したコードの処理 + puts $_ +end +``` + +また、`Kernel#gsub` という `$_ = $_.gsub(...)` と同様の処理をおこなうメソッドが生えてくる。今回は `String#gsub!` も使うので、shebang の分を回収できれば短縮になりそうだ。 + +というわけで、実はこれまでも shebang での短縮は何度か試していた。しかし、いずれも 1 byte 増えたり変化しなかったりで成果を上げられずにいた。回答 E についても同様に、以下のようなコードを作っていた。 + +```ruby +#!ruby -p +?A.upto(?Z){$_.scan(/(?=(.\B.))/).tally.max_by{_2}in[b],1or($*<