[Perlbatross](https://perlbatross.kayac.com/) YAPC::Hiroshima 2024での限定(?)コンテンツ。 ## Hole 1: Split of Grapheme 提出解: ```perl (chop,utf8::decode$_)^print/(\X)\X?/g,quot;,/\X(\X)/g,$/for<> ``` 最初は偶数奇数を取り出す方法を思いつかなくて ```perl print`python3 -c'for s in open(0):s=s[:-1];print(s[0::2],s[1::2])'` ``` のようなものを考えていた。`python`は呼べなかったが`python3`だといけた。が結局これではgrapheme clusterを正しく扱えないので`WA`。エラーの出力が分かりづらくてハマったが とにかくPythonを使うのは無理そうだった。 Rubyなら`grapheme_clusters`ていうメソッドがある! [class String (Ruby 3.3 リファレンスマニュアル)](https://docs.ruby-lang.org/ja/latest/class/String.html#I_GRAPHEME_CLUSTERS) と思って実装してみたものの、そもそも`ruby`はジャッジサーバから呼べないようだった… ようやく正規表現で `/(\X)\X/g` や `/\X(\X)/` することで偶数番目・奇数番目のものを取り出せることに気付く。しかもそのまま`print`に渡せば勝手に繋げて出力されてくれる、便利! 勝手に奇数文字のテストケースを想定してしまっていて、`chop`(`chomp`)が必要で偶数番目のものは後続が無くても良いように`?`を入れる必要があると思い込んでしまっていた、実際にはこれは要らなかったか…。 `::`の代わりに`'`を使えるのはすっかり忘れていた!勿体なかったな〜 ## Hole 2: The bombe 提出解: ```perl map{for$a(18..ord){tr/ab-z/za-y/}print}<> ``` 最初は`chr`と`ord`使うようなのを考えた。26通り作って選択する。 ```perl for(<>){for$i(0..25){$_=join"",map{chr(97+(-96+ord)%26)}split//;print$_,$/if/yapc/}} ``` が どう考えても効率悪い…。 ようやく`tr///`を使って置換することで1つシフトする処理を書けることに気付いた。 ```perl for(<>){for$i(0..25){tr/ab-z/za-y/;print if/yapc/}} ``` そもそも先頭文字が決まっているのだからシフトすべき量も簡単に求められるのでは。 `ord`で先頭文字のASCII codeが取れるので`y(=121)`からの差分だけ動かせばいいよね。 繰り返し回数を差で計算するが負にならないよう26を足す ```perl for(<>){for$i(0..(ord()-121-1)+26){tr/ab-z/za-y/}print} ``` てか26回で一周するので余りの値だけあればいいよね ```perl for(<>){for$i(0..ord()-18){tr/ab-z/za-y/}print} ``` で、実際必要なのは回数だけなので(`$i`は`$_`を変更させないために入れているだけ) ```perl for(<>){for$i(18..ord){tr/ab-z/za-y/}print} ``` となった。 for文の中でまたfor文だと`$_`を書き換えてしまうので`$i`とか何らかの変数に束縛させる必要があり、これを回避する方法を思いつかなかった… ただ`for(<>){...}`を短くする`map{...}<>`への書き換えテクニックを思い出して最終的な提出解に。 あとは`tr`じゃなくて`y`を使えるのを完全に忘れていたのと`ab-z`は`a-z`で良かった、というのに気付けなかったのが反省。 [KarakasaDcFd さんの回答](https://perlbatross.kayac.com/challenge/2/01HPEKHH0695JPMDDFQQX25FV5) では `eval`と`x`を使って繰り返しを実現していて、なるほど…。 ```perl eval'y/a-z/za-y/;'x(9+ord),print for<> ``` これならfor文は1回で、その中で必要な回数だけの置換を呼び出せる。`eval`が自分の発想に無かったなぁ… ## Hole 3: Apache's Call 提出解: ```perl for(<>){/(\d+).{3} .*?" (.)/;($h[$1]||=[(0)x4])->[$2-2]++}for(@h){print"@$_$/"if$_;last if$_->[3]>10} ``` ログ行からminute(タイムスタンプの「分」)と返却したstatusをまず効率よく取得する必要があり、あとは`5xx`が10を超えたときは終了、というのが厄介。 (ちなみに「即座にプログラムを終了」って書いてあったので`exit`してみたら`WA`になってしまった。なんか納得いかない) まずは正規表現でminuteとstatusを取るところ。 2桁の数字の後`:xx`で秒の表示、その後が半角スペースになる箇所だけ最低限とれればminuteが分かるはず(先頭のhostには運良く?matchしなかった)。 あとは`"`の後に半角スペースが来る箇所が2箇所しか無いので最初のものを取るために`.*?"`で最小マッチング。statusだと分かっていれば先頭の1文字だけ取れば良い。 ```perl /(\d+).{3} .*?" (.)/ ``` 初期実装では`%counter`に入れていたが、これは`@c=(0,0,0,0)`のような4要素の配列に入れておけば `print"@c\n"`のような形で最終的な出力が簡単に出せる。 そしてstatusの先頭文字だけ取っているので`$c[$2-2]++`でカウントを更新できる。 あとは1分ごとのウィンドウを更新されたら別のに入れかえ、`5xx`が10を超えたら終了して最終行も表示しない、というのが厄介。 いっそのこと一旦全部配列につっこんで最後に舐めてやれば、と思いarray referenceの配列を使ってカウントだけするようにしてみた。 ```perl ($a[$1]||=[(0)x4])->[$2-2]++ ``` そうすれば一通りカウント終わったあとにそれを順番に見ていくだけ。存在していない場合はskipすれば良い。 ```perl for(@a){print"@$_"if$_;last if$_->[3]>10} ``` だいぶスッキリできた。 まぁこれはminuteの値が単調増加していく前提なのでhourが繰り上がっていたらおかしくなる嘘解法なわけだけど… それでいうとminuteの値しか取っていない時点でちょうど1時間後のログが続くようなテストケースがあったら`WA`になるはず。テストケースの甘さに救われている感じではある。 反省点としては`.{3}`て書くなら`...`で良かった、というものと`print""`内で改行書くならコードで改行すればよかったか、というところであと2byteは短くできたっぽい ## Hole 4: Pytecode 提出解: ```perl use v5.10;sub p{pop@s}sub u{push@s,@_}@p=<>;o();sub o{my$a=pop;while(@p>$c){$_=$p[$c++];chop;if($a&&/un/){last if!p();$c=$a;next}s/^=/==/;given($_){when(/[\+\-\*\/%<>=]=?/){$i=p();u eval p().$_.$i}when(/^d/){$i=p();u$i,$i if/dup/}when(/^s/){u p(),p()}when(/\.\|cr/){print/cr/?$/:p()}when(/if/){if(p()){o();$c++while$p[$c]!~/then/;$c++}else{$i=1;while($i){$_=$p[$c++];$i++if/if/;$i--if/el/}}}when(/^el\|^th/){last if/el/}when(/^b/){o($c)}default{u$_}}}} ``` 複雑すぎてやってられん!という気持ちとの戦い。 ちょっとずつ参考実装を削っていきつつ `perl -pe's/^\s+#.*$//;s/^ +//;s/\n//' 4.pl | pbcopy` とやって適当に短くしたものを貼って動作確認して、の繰り返し。 どうにかちょうどPar `0`におさまった。 これまた幾らでも嘘解法ができてしまう感じだったのでテストケースに救われているものではある。