delete words やたー

delete wordsがdeadlineを過ぎコードが公開されました。
書いたコードの解説を適当に。
transpose linesのプチネタバレとかもあるし、何より長いので続きを読むで。


始めに書いた129Bのコードは下のようなものでした。

#!ruby -p
a=scan /./
gets$9
gsub /#{a}|#{b=a.reverse}/," "*a.size
sub$&," #{([*$~]-[$&,$9])*' '} "while/#{a*c="(.{12})"}|#{b*c}/m

これは酷い。解説略。


流石にこれでは長すぎるということで、少しアルゴリズムを考え直しました。というか上のはアルゴリズムもクソもないですね。
しばらく考えて「文字行列を90度回転させて単語を消す」という処理を4回もすれば十分ということに気がつきました。

#!ruby -pa
$F<<" "*~/$/
gets$9
4.times{$_=(gsub(*$F).map{|b|b.reverse.scan /./}<<[$/]*12).transpose*""}

こんな感じで103Bに。ちなみにtransposeはボクの嫌いなメソッドの一つです。
二行目は、コマンドラインオプションの-aをうまく利用してgsubに渡す引数の配列([$_," "*$_.size])を作っています。
lオプションを使って「a=$_," "*~/$/」としても長さは同じですが、長さが同じ時は気持ち悪い方を採用しています ('-')!


文字行列の回転に文字数を使いすぎている気がしたので、回転部分に集中するため以前出題されたtranspose linesを考え直すことに。
よくよく考えればこれは「各行の頭の文字を出力し、消す」という処理の繰り返しで実装できる。
これでtranspose linesは46Bから40Bに(その後38Bに縮めました)。
その後delete wordsの方を考え直して、「各行の頭の文字」ではなく「各行の最後の文字」を処理していけばいいじゃあないかな、ということで実装。

#!ruby -pa
$F<<" "*~/$/
gets$9
4.times{gsub *$F;a="";a+=$/while gsub!(/.$/){a+=$&;""};$_=a}

transpose linesからそのまんま持ってきた感じなのでちょっと微妙ですが91Bに。


まだ縮む余地はあります。
サンプルインプットを見ると、同じ向きの単語で出現回数が最も多いのは三番目の「lisp」で三回です。
ということは文字行列を3回転…90度回転を12回もすればgsubではなくsubで済むことになります。subのほうが1B短いので何とかそちらにしたいのですが、そのまま12.timesとしたのでは、「4」から「12」で1B増えるので長さは変わらない。

4.times{gsub *$F;a="";a+=$/while gsub!(/.$/){a+=$&;""};$_=a}
12.times{sub *$F;a="";a+=$/while gsub!(/.$/){a+=$&;""};$_=a}

ここでさらにサンプルインプットを見直します。最初の行を除けば入力は12行です。これは使える、ということで各行に対して繰り返しの処理を行えば12.timesと等価。

#!ruby -pa
$F<<" "*~/$/
gets($9).map{sub *$F;a="";a+=$/while gsub!(/.$/){a+=$&;""};$_=a}

これで88Bに。


次にやることは、気持ちの悪い一時変数を消すことです。回転部分がtranspose linesまんまなのも気に食わないし…
これは各行ごとに置き換えるように処理するように書き直すことで消すことが出来ました。

#!ruby -pa
$F<<" "*~/$/
gets($9).map{sub *$F;gsub(/.+/){a=scan b=/.$/;gsub b,"";a}}

aは各行の最後の文字からなる配列です。gsubによってto_sされることでjoinされた文字列になります。これでさらに縮んで83Bに。


しかしそれでもまだ一時変数が残っている。この手の一時変数は多くの場合無駄なので、何とかして消したい。
処理の順番は、まずscan、次にgsubでないといけません。しかし最終的に評価されないといけないのはscanの値です。普通に考えると一時変数を使うしかないのですが…
Array#-をうまく使うことで解決します。Arrayの演算子は皆、短い癖に良い仕事をしてくれるのでもっと使ってあげるといいです。

#!ruby -pa
$F<<" "*~/$/
gets($9).map{sub *$F;gsub(/.+/){scan(b=/.$/)-[gsub b,""]}}

Array#-は左の要素から右の要素と同じ物を取り除いた配列を返す演算子です。同じ要素が存在しないため、上のコードではそのままの値が最終的に評価されます。これで最終形の82B。


割と綺麗な感じに縮められたのでプチ嬉しかったです。