その1はこちら
その2はこちら
解答、ヒント、解説などは充分な余白を開けた上で表示するので、安心して読んでください。
問題
1 || true || puts("Hello world") || 1
このRubyプログラムに1文字追加して、"Hello world\n"を1度だけ出力するプログラムにしなさい。 なお、例外などで異常終了するものは正答とはみなしません。
RubyKaigi 2019のCookpadのパズルリスペクトです。
なお、hanachinさんが色々な人が出している問題をまとめてくれているので、もっと問題を解きたい方はそちらをご覧ください。
Okinawa.rb Ruby Puzzles 2019/5/15 - #rubykaigi のRubyPuzzleが面白かったので真似しました。Ruby 2.7でしか動きません。 · GitHub
ちなみに、この問題はある限られたRubyでのみ解くことができます。 この「限られたRuby」がなんなのかはヒントに書きます。
ヒント
以下にヒント(この問題を解けるRubyについて)を少しずつ書いていきます。適当にスクロールしてください。 ヒント2まであり、少しずつ核心に近づいていきます。
ヒントなしで解くのは難しいと思いますが、ヒント2はほぼ答えです。ちょうどよいヒントを出すのが難しいので…
ヒント その1
時事ネタです。
ヒント その2
この問題は、Ruby 2.7-devの特定のリビジョン間でのみ解くことができます。
- ruby-trunk-changes 2019-06-13 - ruby trunk changes
- ruby-trunk-changes 2019-06-14 - ruby trunk changes
上の記事を読むとなんとなくわかると思います。
答え
答えです。
1 || true |>| puts("Hello world") || 1
解説
pipeline operator
この問題の鍵は、6/13に追加され、6/14には弱体化されたpipeline operator(|>
)です。
これはメソッドチェインを括弧なしで書ける.
だと思えば良いでしょう。
# . は括弧が必要 foo .bar(1) .baz(2) # |> は括弧が要らない foo |> bar 1 |> baz 2
これは.
と同じようなものなので、ruby/ruby@d365fd5以前は1 |>+ 2
のように演算子を呼び出すこともできましたが、このコミットで禁止されてしまいました。
色々と悪用できそうなだけに残念ですね。
なお、pipeline operatorはまだ昨日入ったばかりの構文なので、今後仕様が変わる可能性は充分にあります。
解き方
この問題のメインは1 || true || puts("Hello world")
で、最後の|| 1
はおまけです(後ほど解説します)。
つまり、1 || true
をなんとかして偽にして2つ目の||
の右辺を実行するか、2つ目の||
を別のなにかに置き換えてやる必要があります。
何故ならば||
は短絡評価される演算子であり、左辺が真であれば右辺は評価されないためです。
そして、今回の解法はその後者です。||
を|>|
に置き換えていますね。これは要するに、true ||
をTrueClass#|
の呼び出しに変えています。TrueClass#|
は|
と同じような動きをしますが、短絡評価を行いません。そのためputs
が実行されるというわけです。
おまけ
|| 1
は別解を防ぐために入れています。また、プログラムの先頭の1
も同じ役割を持っています。
この問題の初期バージョンは次のようにシンプルな形でした。
true || true || puts("Hello world")
この問題も同様に|>
を使って解くことができますが、もう1つ解ができてしまいます。それは%
です。
true |%| true || puts("Hello world")
%
万能ですね、やばい。
%
は文字列リテラルであるため、これはtrue | "| true |" | puts("Hello world")
と同じように評価されます。
先述したとおり|
は短絡評価を行わないメソッドなので、puts(Hello world)
が評価されるわけです。
これを防ぐために、最初のtrue
を1
に置き換えました。
TrueClass#|
は||
と同様の動きをしますが、Integer#|
は各ビットの論理和を取るメソッドです。そして、Integer#|
の右辺に文字列を取ることはできず、エラーとなります。
これで%
を防ぐことはできましたが、今度は|>
を使った解法までエラーになってしまいます。これはputs
がnil
を返し、かつInteger#|
にnil
を渡した場合もエラーになってしまうためです。
この問題の最後の|| 1
は、このエラーを防ぐためのものでした。
計らずも寿命が1日のパズルを作ってしまいましたが、お楽しみいただけましたら幸いです。