aboutsummaryrefslogtreecommitdiffhomepage
path: root/services/nuldoc/public/blog/posts/2025-11-27/anybatross-writeup/index.html
blob: 09e2fa1a5a2acc80d25b8f00bf8bc9facc6cde98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
<!DOCTYPE html>
<html lang="ja-JP">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="nsfisis">
    <meta name="copyright" content="&copy; 2025 nsfisis">
    <meta name="description" content="YAPC::Fukuoka 2025 に際してカヤックさんが開催されていたコードゴルフコンテスト、Anybatross に参加して優勝した。">
    <meta name="keywords" content="コードゴルフ,Perl,Ruby,YAPC">
    <meta property="og:type" content="article">
    <meta property="og:title" content="カヤックさん開催のコードゴルフコンテスト Anybatross に参加して優勝した|REPL: Rest-Eat-Program Loop">
    <meta property="og:description" content="YAPC::Fukuoka 2025 に際してカヤックさんが開催されていたコードゴルフコンテスト、Anybatross に参加して優勝した。">
    <meta property="og:site_name" content="REPL: Rest-Eat-Program Loop">
    <meta property="og:locale" content="ja_JP">
    <meta name="Hatena::Bookmark" content="nocomment">
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
    <title>カヤックさん開催のコードゴルフコンテスト Anybatross に参加して優勝した|REPL: Rest-Eat-Program Loop</title>
    <link rel="stylesheet" href="/style.css?h=c171793a210d62f7ff2ddf54208f34e5">
  </head>
  <body class="single">
    <header class="header">
      <div class="site-logo">
        <a href="https://nsfisis.dev/">nsfisis.dev</a>
      </div>
      <div class="site-name">
        REPL: Rest-Eat-Program Loop
      </div>
      <nav class="nav">
        <ul>
          <li>
            <a href="https://about.nsfisis.dev/">About</a>
          </li>
          <li>
            <a href="/posts/">Posts</a>
          </li>
          <li>
            <a href="/tags/">Tags</a>
          </li>
        </ul>
      </nav>
    </header>
    <main class="main">
      <article class="post-single">
        <header class="post-header">
          <h1 class="post-title">カヤックさん開催のコードゴルフコンテスト Anybatross に参加して優勝した</h1>
          <ul class="post-tags">
            <li class="tag">
              <a class="tag-inner" href="/tags/code-golf/">コードゴルフ</a>
            </li>
            <li class="tag">
              <a class="tag-inner" href="/tags/perl/">Perl</a>
            </li>
            <li class="tag">
              <a class="tag-inner" href="/tags/ruby/">Ruby</a>
            </li>
            <li class="tag">
              <a class="tag-inner" href="/tags/yapc/">YAPC</a>
            </li>
          </ul>
        </header>
        <nav class="toc">
          <h2>目次</h2>
          <ul>
            <li>
              <a href="#section--intro">はじめに</a>
            </li>
            <li>
              <a href="#section--hole-1">Hole 1</a>
              <ul>
                <li>
                  <a href="#section--hole-1--answer">回答 (45 byte)</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#section--hole-2">Hole 2</a>
              <ul>
                <li>
                  <a href="#section--hole-2--answer-a">回答 A (107 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-b">回答 B (107 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-c">回答 C (106 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-d">回答 D (104 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-e">回答 E (103 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-f">回答 F (103 byte)</a>
                </li>
                <li>
                  <a href="#section--hole-2--answer-g">最終回答 (102 byte)</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#section--llm">LLM のコードゴルフ性能</a>
            </li>
            <li>
              <a href="#section--outro">おわりに</a>
            </li>
          </ul>
        </nav>
        <div class="post-content">
          <section id="changelog">
            <h2><a href="#changelog">更新履歴</a></h2>
            <ol>
              <li class="revision">
                <time datetime="2025-11-27">2025-11-27</time>: 公開
              </li>
            </ol>
          </section>
          <section id="section--intro">
            <h2><a href="#section--intro">はじめに</a></h2>
            <p>
              <a href="https://yapcjapan.org/2025fukuoka/" rel="noreferrer" target="_blank">YAPC::Fukuoka 2025</a> に際してカヤックさんが開催されていたコードゴルフコンテスト、<a href="https://perlbatross.kayac.com/contest/2025fukuoka" rel="noreferrer" target="_blank">Anybatross</a> に参加し、優勝した。この記事では自分の回答について解説する。
            </p>
            <p>
              なお今回のシステムでは、現在の自分のスコア以下のコードを開催中も閲覧できる仕様だった。Hole 1 では特に使わなかったが、Hole 2 では途中他の方のコードをベースに進めた箇所がある (詳しくは後述)。
            </p>
            <p>
              このコンテストは何度か開催されており、<a href="https://perlbatross.kayac.com/contest/2024hiroshima" rel="noreferrer" target="_blank">前々回</a> に参加したときは総合 2 位だった (前回開催は不参加)。
            </p>
          </section>
          <section id="section--hole-1">
            <h2><a href="#section--hole-1">Hole 1</a></h2>
            <section id="section--hole-1--answer">
              <h3><a href="#section--hole-1--answer">回答 (45 byte)</a></h3>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">print</span><span style="color:#24292E">$a+=$\=</span><span style="color:#005CC5">y</span><span style="color:#032F62">/8B/0/</span><span style="color:#24292E">+</span><span style="color:#005CC5">y</span><span style="color:#032F62">/0469ADO-R//</span><span style="color:#24292E">.$/,</span><span style="color:#032F62">","</span><span style="color:#D73A49">for</span><span style="color:#24292E">&#x3C;></span></span></code></pre>
              </div>
              <p>
                Hole 1 については同一言語・同一スコアの回答が複数あるので詳細は省略する。
              </p>
            </section>
          </section>
          <section id="section--hole-2">
            <h2><a href="#section--hole-2">Hole 2</a></h2>
            <p>
              こちらは縮めていった過程も記載する。
            </p>
            <section id="section--hole-2--answer-a">
              <h3><a href="#section--hole-2--answer-a">回答 A (107 byte)</a></h3>
              <p>
                最終スコアを見ると 4 位タイ (107 byte) が多く、3 位以上の回答と明確にアルゴリズムの差があるのでここから解説をスタートしようと思う。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#E36209">s</span><span style="color:#D73A49">=</span><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){(b,),m</span><span style="color:#D73A49">=</span><span style="color:#24292E">s.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span></span>
<span class="line"><span style="color:#24292E">m</span><span style="color:#D73A49">></span><span style="color:#005CC5">1</span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#24292E">(s.</span><span style="color:#6F42C1">gsub!b</span><span style="color:#24292E">,it;$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,s</span></span></code></pre>
              </div>
              <p>
                変数名などの細かい差異を除けば他の 107 byte 回答と同じだが、 <code>String#scan</code> に渡す正規表現にこれを採用していたのは私だけだったのではないだろうか。 <code>/(?=(\S\S))/</code> や <code>/(?=(\w\w))/</code> と比べて短くはならないので意味はない。
              </p>
              <p>
                <code>String#scan</code> はマッチした文字列を「消費」するので、重複した範囲を切り出す必要のある bi-gram の生成には使えない。そこで、肯定先読み (<code>(?=pattern)</code>) を使う。これは <code>^</code> や <code>\b</code> などと同様に、条件を指定しているだけでゼロ幅なので、<code>String#scan</code> に消費されない。これを使えば bi-gram の生成ができる。ただし、<code>s.scan(/\S\S/)</code> などと書いた場合とは異なり、返る配列が <code>[[&quot;la&quot;], [&quot;au&quot;], [&quot;un&quot;], ...]</code> のような形でネストすることに注意。
              </p>
              <p>
                出現頻度を調べるのには <code>Enumerable#tally</code> が使える。Perl に対する強烈な優位性はここで、これを使えばキーが各配列の要素、値が出現回数であるようなハッシュが一発で生成できる。
              </p>
              <p>
                <code>Enumerable#max_by</code> で最頻値を取ってきた後は、多重代入を使って必要な値を取り出している。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#E36209">x</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> [[</span><span style="color:#032F62">"la"</span><span style="color:#24292E">], </span><span style="color:#005CC5">3</span><span style="color:#24292E">]</span></span>
<span class="line"><span style="color:#24292E">(b,),m </span><span style="color:#D73A49">=</span><span style="color:#24292E"> x</span></span>
<span class="line"><span style="color:#6A737D"># => b = "la", m = 3</span></span></code></pre>
              </div>
              <p>
                置換テーブルのデータは <code>$*</code> へと追加しているが、これは Ruby の特殊変数で、本来は <code>Object::ARGV</code> を指す。ここでは単に最初から空配列で初期化されている便利な入れ物として用いている。
              </p>
              <p>
                その他、<code>?A</code> や <code>String#*</code>、<code>it</code>、numbered parameters などの細かいテクニックについては、「Ruby コードゴルフ」で調べるか、最近の Ruby のリリースノートを読むと見つかるはず。
              </p>
            </section>
            <section id="section--hole-2--answer-b">
              <h3><a href="#section--hole-2--answer-b">回答 B (107 byte)</a></h3>
              <p>
                回答 A をぐっと睨むと、<code>m&gt;1&amp;&amp;(...)</code> の括弧を削りたくなる。しかしそれには <code>m&gt;1&amp;&amp;</code> がどうしても邪魔になる。というわけで終了条件を工夫することでなんとか <code>m</code> を排除できないかを考えた。それがこちら。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#E36209">s</span><span style="color:#D73A49">=</span><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){(b,),</span><span style="color:#D73A49">=</span><span style="color:#24292E">(</span><span style="color:#005CC5">?_</span><span style="color:#D73A49">+</span><span style="color:#24292E">s).</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span></span>
<span class="line"><span style="color:#24292E">$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b </span><span style="color:#D73A49">if</span><span style="color:#24292E"> s.</span><span style="color:#6F42C1">gsub!b</span><span style="color:#24292E">,it}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,s</span></span></code></pre>
              </div>
              <p>
                <code>s</code> の先頭に番兵 <code>_</code> を置くことで、bi-gram の出現頻度がすべて 1 になったとき、<code>b</code> へと代入される値が「<code>_</code> + (<code>s</code> の先頭の文字)」になる。これを <code>String#gsub!</code> で置き換えようとすると、そのような文字列は <code>s</code> 中にないので置換が発生しない。<code>String#gsub!</code> は置換が起きなかったとき <code>nil</code> を返すので、これを使って条件分岐ができる。<code>&amp;&amp;</code> だと優先度の関係から <code>String#gsub!</code> の括弧が省略できないが、後置 if なら省略できる。
              </p>
              <p>
                しかし、これだけでは回答 A とスコアは変わらない。これを短縮したものがこちら。
              </p>
            </section>
            <section id="section--hole-2--answer-c">
              <h3><a href="#section--hole-2--answer-c">回答 C (106 byte)</a></h3>
              <p>
                <code>Kernel#gets</code> は、入力を特殊変数 <code>$_</code> へ代入する。これは Perl 由来の挙動で、Ruby にはいくつか <code>$_</code> を参照するものがある。これを使って変数 <code>s</code> を置き換えると次のようになる。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){(b,),</span><span style="color:#D73A49">=</span><span style="color:#032F62">"_</span><span style="color:#24292E">#$_</span><span style="color:#032F62">"</span><span style="color:#24292E">.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span></span>
<span class="line"><span style="color:#24292E">$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b </span><span style="color:#D73A49">if</span><span style="color:#24292E">$_.</span><span style="color:#6F42C1">gsub!b</span><span style="color:#24292E">,it}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,$_</span></span></code></pre>
              </div>
              <p>
                これで 1 byte 縮む。
              </p>
            </section>
            <section id="section--hole-2--answer-d">
              <h3><a href="#section--hole-2--answer-d">回答 D (104 byte)</a></h3>
              <p>
                回答 C を眺めると、<code>b</code> への代入に文字を費やしすぎている。これを <code>String#gsub!</code> の第一引数に直接書いてはどうか。更に、直前のマッチしたパターンを指す特殊変数 <code>$&amp;</code> を使えば、変数 <code>b</code> を排除できる。それがこちら。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">$&#x26;</span><span style="color:#D73A49">if</span><span style="color:#24292E">$_.</span><span style="color:#6F42C1">gsub!</span><span style="color:#032F62">"_</span><span style="color:#24292E">#$_</span><span style="color:#032F62">"</span><span style="color:#24292E">.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}[</span><span style="color:#005CC5">0</span><span style="color:#24292E">][</span><span style="color:#005CC5">0</span><span style="color:#24292E">],it}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,$_</span></span></code></pre>
              </div>
              <p>
                コードゴルフとして <code>[0][0]</code> は気になるところだが、それでも 2 byte 一気に縮まった。
              </p>
            </section>
            <section id="section--hole-2--answer-e">
              <h3><a href="#section--hole-2--answer-e">回答 E (103 byte)</a></h3>
              <p>
                回答 D を提出したことで tompng 氏のスコアを越え、氏のコードを閲覧できるようになった。そこから少し変更したものが、mame 氏と (変数名などの些事を除いて) 同じ以下のコードである。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#E36209">s</span><span style="color:#D73A49">=</span><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){s.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span><span style="color:#D73A49">in</span><span style="color:#24292E">[b],1</span><span style="color:#6F42C1">or</span><span style="color:#24292E">($*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b;s.</span><span style="color:#6F42C1">gsub!b</span><span style="color:#24292E">,it)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,s</span></span></code></pre>
              </div>
              <p>
                ここまでとは大きく異なる戦略で終了条件を判定している。使われているのはパターンマッチで、<code>in</code> がマッチの有無を <code>true</code> / <code>false</code> で返すことを利用している。<code>or</code> を用いて、最頻値の出現回数が 1 でないなら置換処理を継続する。
              </p>
              <p>
                パターンマッチの利用については途中何度か検討したが、1 でないときに処理を実行するという方針で実装しようとしてしまい、上手く短縮できなかった。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># これは 106 byte</span></span>
<span class="line"><span style="color:#E36209">s</span><span style="color:#D73A49">=</span><span style="color:#005CC5">gets</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){s.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span><span style="color:#D73A49">in</span><span style="color:#24292E">[b],</span><span style="color:#005CC5">2</span><span style="color:#24292E">..</span><span style="color:#6F42C1">and</span><span style="color:#24292E">($*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b;s.</span><span style="color:#6F42C1">gsub!b</span><span style="color:#24292E">,it)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span><span style="color:#24292E">,s</span></span></code></pre>
              </div>
            </section>
            <section id="section--hole-2--answer-f">
              <h3><a href="#section--hole-2--answer-f">回答 F (103 byte)</a></h3>
              <p>
                さて、ヒントを求めて <a href="https://techblog.kayac.com/yapc2024hakodate-perlbatross-result" rel="noreferrer" target="_blank">前回開催の公式解説ブログ</a> を読んでいたところ、次のような興味深い記述を発見した。
              </p>
              <blockquote>
                <p>
                  参加者のみなさんの最短解はshebangを書いてPerlのオプションを設定していい感じにやるものでしたが、(中略) 今回はチート抑止みたいなところの意図でperlコマンドを実行する方式になったので、ちゃんとshebangを書けば効くようになっていたのでした。
                </p>
              </blockquote>
              <p>
                Shebang が使えるのなら、Ruby にも Perl に由来するオプションがいくつかあるので、似たような手段で短縮できるのではないか?
              </p>
              <p>
                <code>ruby</code> で <code>-p</code> を付けると、以下のようなコードを書いたかのように動作する。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">while</span><span style="color:#005CC5"> gets</span></span>
<span class="line"><span style="color:#24292E">  ... </span><span style="color:#6A737D"># 記載したコードの処理</span></span>
<span class="line"><span style="color:#005CC5">  puts</span><span style="color:#24292E"> $_</span></span>
<span class="line"><span style="color:#D73A49">end</span></span></code></pre>
              </div>
              <p>
                また、<code>Kernel#gsub</code> という <code>$_ = $_.gsub(...)</code> と同様の処理をおこなうメソッドが生えてくる。今回は <code>String#gsub!</code> も使うので、shebang の分を回収できれば短縮になりそうだ。
              </p>
              <p>
                というわけで、実はこれまでも shebang での短縮は何度か試していた。しかし、いずれも 1 byte 増えたり変化しなかったりで成果を上げられずにいた。回答 E についても同様に、以下のようなコードを作っていた。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">#!ruby -p</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){$_.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span><span style="color:#D73A49">in</span><span style="color:#24292E">[b],1</span><span style="color:#6F42C1">or</span><span style="color:#24292E">($*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b;</span><span style="color:#005CC5">gsub</span><span style="color:#24292E"> b,it)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span></span></code></pre>
              </div>
              <p>
                しかしこれは 103 byte で縮められない。にっくきは <code>gsub</code> と <code>b</code> の間のスペースである。せっかく <code>s.gsub!</code> を <code>gsub</code> にしたのに、後ろが記号でなくなったことでスペースが生じている。といって、括弧を付けるのも上手くはいかない。
              </p>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># これも同じく 103 byte</span></span>
<span class="line"><span style="color:#6A737D">#!ruby -p</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){$_.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span><span style="color:#D73A49">in</span><span style="color:#24292E">[b],1or$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b</span><span style="color:#D73A49">&#x26;&#x26;</span><span style="color:#005CC5">gsub</span><span style="color:#24292E">(b,it)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span></span></code></pre>
              </div>
              <p>
                外側の括弧を移動させてくれば <code>gsub</code> と <code>b</code> の間のスペースを消せるが、<code>;</code> を <code>&amp;&amp;</code> にせねばならず失敗する。この問題を解決したのが最終回答の 102 byte コードである。
              </p>
            </section>
            <section id="section--hole-2--answer-g">
              <h3><a href="#section--hole-2--answer-g">最終回答 (102 byte)</a></h3>
              <div class="codeblock">
                <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">#!ruby -p</span></span>
<span class="line"><span style="color:#005CC5">?A</span><span style="color:#24292E">.</span><span style="color:#6F42C1">upto</span><span style="color:#24292E">(</span><span style="color:#005CC5">?Z</span><span style="color:#24292E">){$_.</span><span style="color:#6F42C1">scan</span><span style="color:#24292E">(</span><span style="color:#032F62">/(?=(.</span><span style="color:#22863A;font-weight:bold">\B</span><span style="color:#032F62">.))/</span><span style="color:#24292E">).</span><span style="color:#6F42C1">tally</span><span style="color:#24292E">.</span><span style="color:#6F42C1">max_by</span><span style="color:#24292E">{</span><span style="color:#005CC5">_2</span><span style="color:#24292E">}</span><span style="color:#D73A49">in</span><span style="color:#24292E">[b],1or$*</span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E">it</span><span style="color:#D73A49">+</span><span style="color:#005CC5">?:</span><span style="color:#D73A49">+</span><span style="color:#24292E">b</span><span style="color:#D73A49">%</span><span style="color:#005CC5">gsub</span><span style="color:#24292E">(b,it)}</span></span>
<span class="line"><span style="color:#005CC5">puts</span><span style="color:#24292E">$*</span><span style="color:#D73A49">*</span><span style="color:#005CC5">?,</span></span></code></pre>
              </div>
              <p>
                <code>String#%</code> は文字列のフォーマット処理をおこなう演算子だが、ここでは特にフォーマット目的で呼んでいるわけではない。ここで重要なのは、この演算子が特に副作用を持たず、どんな型でも右辺に取れることである。<code>b</code> の中身にフォーマット指定子はない (<code>%</code> などの記号が入力されないことが問題文から分かる) ので、誤って動作を壊してしまうおそれもない。
              </p>
              <p>
                これによって Hole 2 の単独 1 位スコアとなった。
              </p>
            </section>
          </section>
          <section id="section--llm">
            <h2><a href="#section--llm">LLM のコードゴルフ性能</a></h2>
            <p>
              ところで、今回スコア短縮に行き詰まったあたりから LLM を使ってみた (コンテストのレギュレーションとして明示的に利用が許可されている)。
            </p>
            <p>
              結論から言うと役には立たなかった。サンプルコードを縮めるくらいの用途には使えるかもしれないが (未検証)、すでに人間がある程度のところまで縮めたコードを更に短縮するのはまだ荷が重いようだ。そもそもそれで縮まって楽しいのかという別の問題もある。
            </p>
            <p>
              なお、LLM は文字数カウントを大の苦手としているので、縮めた (と言い張っている) コードを <code>wc</code> コマンドに渡し、その結果を LLM に見てもらった方がよい。
            </p>
          </section>
          <section id="section--outro">
            <h2><a href="#section--outro">おわりに</a></h2>
            <p>
              楽しかったです。運営のみなさま、ありがとうございました!
            </p>
          </section>
        </div>
      </article>
    </main>
    <footer class="footer">
      &copy; 2021 nsfisis
    </footer>
  </body>
</html>