この記事は、ヤプリ&フラー 合同アドベントカレンダー Advent Calendar 2025の11日目の記事です。
Anybatrossとは、コードゴルフコンテストです。コードゴルフコンテストでは、与えられた仕様を満たすプログラムをいかに短く書けるかを競います。例年開催されていて、今までは Perl しか使えなかったようですが、今年からRuby、Python、JavaScript、PHPが使えるようになったそうです。この中で一番馴染みのある JavaScript を使用して、コードゴルフに挑戦してみました。
問題は Hole1, Hole2 の2つありましたが、Hole1だけに話を限定します。この記事では、はじめにHole1の問題文と最終提出したコードを示します。次に、そのコードに辿りつくまでにどういう手段をとったか、その概要を説明します。
Hole1の問題文
アルファベットのAやBにあるような、文字の中にある閉じた空間のことをカウンターといいます。
0〜9までの10種と、アルファベット大文字のA〜Zの26種、合計36種の文字やその他の記号を利用した文字列が渡されるので、カウンターの数を数えてください。アルファベット小文字は来ません。その他の記号のカウンターは数えなくてよいです。
1行ずつ数えて、その行までの累積個数と、その行での出現個数を出力してください。
入力例
KAYAC
YAPC FUKUOKA
WHITE
POWAWA
*STRONG*: ZERO
出力例
2,2
6,4
6,0
10,4
14,4
例えば、HELLO, WORLDだと、カウンターは二つのOと一つRから3になります。
提出コード
結論として、自分の最高スコアを記録したコードは以下です。Hole1はParが100bytesであり、以下のコードは93bytesであるため、スコアは-7になります。この結果は、JavaScriptの中では2位であり、1位は-10でした。
悔しい。replaceを使う発想に至ることで、スコアを大きく縮めることができたのですが、正規表現に無駄がある気がしています。色々格闘したんですがうまく縮められず...
1位の方もreplaceを使っているんですかね...
for(a=0;t=0,std.in.getline()?.replace(/[0469ADO-R]|(8|B)/g,(_,n)=>t+=n?2:1);print([a+=t,t]));
初期コード(サンプル)
JavaScriptを書くのは久しぶりなので、サンプルのコードから削っていくことにしました。用意されていたコードは以下です。パッと見て、COUNTER_MAPの値が0であるものは初期化する必要なさそう、というのは分かると思います。そういうところから削っていきました。
const COUNTER_MAP = {
'0': 1,
'1': 0,
'2': 0,
'3': 0,
'4': 1,
'5': 0,
'6': 1,
'7': 0,
'8': 2,
'9': 1,
'A': 1,
'B': 2,
'C': 0,
'D': 1,
'E': 0,
'F': 0,
'G': 0,
'H': 0,
'I': 0,
'J': 0,
'K': 0,
'L': 0,
'M': 0,
'N': 0,
'O': 1,
'P': 1,
'Q': 1,
'R': 1,
'S': 0,
'T': 0,
'U': 0,
'V': 0,
'W': 0,
'X': 0,
'Y': 0,
'Z': 0,
};
const input = std.in.readAsString();
const lines = input.trim().split('\n');
const results = [];
let accumulatedCount = 0;
// 行ごとに処理する
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
// 1行を1文字ずつ分割
const chars = lines[lineNum].split('');
// 1文字ずつカウンターの数を合計
let totalCount = 0;
for (const char of chars) {
totalCount += COUNTER_MAP[char] ?? 0;
}
accumulatedCount += totalCount;
results.push(accumulatedCount + ',' + totalCount);
}
// 1行ごとに改行
for (const result of results) {
console.log(result);
}
ランタイムの仕様確認
どのようなランタイムがジャッジシステム1に使われているかを確認しました。その知識は、入力や出力とそれらに関係するロジックでコードを縮めるのに有用でした。pdfファイルとずっと睨めっこしていた記憶があります。
- ソースコード: https://github.com/bellard/quickjs
- ドキュメント:
minify ツールの利用
インデントや改行が含まれないコードは読みにくいので、提出の際に minify して提出するようにしました。minify に使ったツールは以下です。
ただ、minify は空白文字の削除や変数名の短縮程度のことしかしないものだと思っていたのですが、whileをforで書き換えるなど、自分の想定を超える最適化を行うことがありました。振り返ると、自分の力量を超える最適化はされなかったはずですが、ちゃんと何が行われるか理解して使うべきだったと反省しています。
生成AI活用
ルール上、生成AIの活用は禁止されていないので、ChatGPTとGeminiの2つのAIアシスタントを活用しました。バージョンはChatGPT 5.1, Gemini2.5です。主に言語の構文に関する質問や、配列やオブジェクトなどのデータ構造や正規表現で有名なコードゴルフのテクニックはないか質問しました。
具体的には以下のような質問です。(原文ママ)
- javascript のショートコーディングのテクニックを列挙してください
- さんこうかん演算子と パターン を使って A or B なら ... する
- javascript Map の初期化方法を教えてください。また コードゴルフのテクニックも教えてください
- NaN なら 0 それ以外は 1
- console.log のコードゴルフ版をお願いします
一応、コードそのものを渡して短縮してもらうような質問は、楽しみを損ねてしまうため、しないようにしましたが、いくつかの質問からコードの全体像を推測されている感じもあって、あまり守れていなかったと思います。
下位のプレイヤーのコードをみて参考にする
ランキングで自分よりスコアが下のコードは閲覧可能です。
これを利用して、他の人のテクニックを参考にし、スコアを縮めました。
Perl での挑戦
ランキングを見て分かるようにHole1の上位はPerlで占められています。言語差だけではない、まだ知らないテクニックがあると思い、Perlに挑戦しました。ただ、これは別のブーム2が来てしまったので、Perlの可能性に気づく前に途中で断念しています。
ちなみに、生成AIで出力結果が Perl のメタ文字によって書式が崩れる問題がありました。
また、そもそも情報が少ない感じがあります。
さいごに、感想
考えてスコアを縮める過程や、AIアシスタントとのやりとりを通して、少しずつJavaScriptに詳しくなっていく過程が、ゲームをやっているようであり、楽しかったです。
また、コードが短ければ何をやってもだいたいOKな普段とは違うプログラミングが楽しめてよかったです。例えば、配列の最大値を取得するのに、for文で配列を見るのではなく、計算量を犠牲にしてソートしてから、先頭の値を取得したり、型を無視してオブジェクトで宣言したものを配列で再利用するなどです。
今回 JavaScriptでしか挑戦できなかったので、他の言語でやってみたい思いました。PerlやRubyの猛者たちはいったい何をやっているのか知りたい気持ちがあります。
おまけ: ちなみに Go は?
背景: 弊社はサーバーサイドはGoで書いていて、普段はGoを使っています。
ビルドできる最小のコードは以下です。
mainパッケージとmain関数が必ず必要になるのが邪魔ですね。
なくしてもビルドできるように言語仕様を変える issue を立てましょう。😵💫
package main;func main(){}