Okinawa.rb Ruby Puzzles 2019/5/15 hanachinパズル解説
RubyKaigi 2019のCookpad Ruby Puzzlesがとてもおもしろくてお返しパズルをつくってみました。パク^H^Hリスペクト。
ここから先、余白を空けたりしてから解答と解説を載せていきます。
1問目
事前にSolverを通していなかったので潰せていない解答が2つありました。 まず想定解から説明します。
まずパズルの動作です。case
in
でx
に1.010101
を代入し、^x
でx
の中身とマッチさせています。
マッチしなかった場合Hello world
が出力されます。
"Hello world".then do case x = 1.010101 in ^x puts "Goodbye world" end rescue NoMatchingPatternError puts @1 end
case
in
では===
メソッドを使って比較されます。なのでx
に!(x === x)
を満たすような値を代入するとマッチしなくなります。
ところでRubyの数値リテラルでは0ではじまる数字は8進整数とみなされます。
- 0377
- 0o377
- https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#num
8進整数
1.010101
をよく見ると.
の右側が0ではじまっていますね。.
を付け足すと1から8進数の10101の範囲のRangeオブジェクトになります。
1..010101 # => 1..4161
このRangeオブジェクトを===
で比べてみましょう。
(1..010101) === (1..010101) # => false
ということで想定解は「1.
の後ろに.
を付け足す」でした。
"Hello world".then do case x = 1..010101 in ^x puts "Goodbye world" end rescue NoMatchingPatternError puts @1 end
0はじまりで8進数書けるのを知らないと解きづらいかな〜?と思って考えました。 パターンマッチでマッチさせない目的でプログラムを書くのはあまりないと思うので書いてみた。1
想定外の解
x
とx
を比較したらマッチするのは当たり前なので、x
とx
を比較しないようにすれば解けます。
xの前に単項演算子の!
や-
を付け加えると以下のような動作になり、マッチしなくなります。2
- xに値を代入
- !xや-xを評価
- !xや-xとxを比較
"Hello world".then do case !x = 1.010101 in ^x puts "Goodbye world" end rescue NoMatchingPatternError puts @1 end
"Hello world".then do case -x = 1.010101 in ^x puts "Goodbye world" end rescue NoMatchingPatternError puts @1 end
2問目
これは最初の状態だとSyntaxErrorが出て実行できないです。実行できないプログラムをなおすの難しそう♡という感じの気持ちで書いた。
def say Proc.new end greeting = say { case "Hello world" in x => y if x = 1 then 1 else 0 end & 1 => y } puts greeting.call(1)
知らない人も多いと思いますがProc.new
にブロックをわたしていなくてもProc.new
を読んでいるメソッドにブロックが渡されていると、なぜかうまく動きます。
% ruby -e 'def foo; Proc.new.call; end; foo { puts :hi }' -e:1: warning: Capturing the given block using Proc.new is deprecated; use `&block` instead hi
来歴紹介
ブロック引数がなかったころの名残機能?
— _ko1 (@_ko1) 2018年7月19日
使わないでと言われると使いたくなるので使った。
つかわないで!
— _ko1 (@_ko1) 2019年4月24日
ところでこれブロックをわたしているように見えるじゃないですか?
greeting = say { case "Hello world" in x => y if x = 1 then 1 else 0 end & 1 => y }
でもこころの目でよーくみると最後の=>
がハッシュロケットに見えてきませんか? きますよね?
# greeting = say ←みなかったことにする { よくわからない長い式 & 1 => y }
そこでHash#to_proc
です。
https://docs.ruby-lang.org/ja/latest/method/Hash/i/to_proc.html
to_proc -> Proc
[permalink][rdoc]self に対応する Proc オブジェクトを返します。
h = {1 => 10, 2 => 20, 3 => 30} [1, 2, 3].map(&h) # => [10, 20, 30]
ということで答えは「sayの後ろの{
の前に&
を付け足す」でした。
def say Proc.new end greeting = say &{ case "Hello world" in x => y if x = 1 then 1 else 0 end & 1 => y } puts greeting.call(1)
3問目
これは比較的簡単なんですが目パーサーを混乱させるために記号をいっぱい使いました。
?...?.?%??.:puts.:[]==="Hello world":%??
実行するとMethodクラスに[]=
メソッドは無いとおこられます。
% ruby hanachin-puzzle-3.rb hanachin-puzzle-3.rb:1: warning: string literal in condition hanachin-puzzle-3.rb:1: warning: string literal in condition hanachin-puzzle-3.rb:1: warning: flip-flop is deprecated Traceback (most recent call last): hanachin-puzzle-3.rb:1:in `<main>': undefined method `[]=' for class `Method' (NameError)
ところでMehtodクラスには[]
メソッドはある。
Method.instance_methods(false) # => [:parameters, :arity, :<<, :curry, :>>, :to_proc, :==, :===, :hash, :inspect, :to_s, :clone, :[], :call, :original_name, :owner, :unbind, :super_method, :name, :source_location, :eql?, :receiver]
なので単純に[]
と===
の間にスペースをあけるととけます。
?...?.?%??.:puts.:[] ==="Hello world":%??
やっていることは比較的単純です。
%??
と出てる部分はただの空文字列なので置き換えます。
?...?.?"".:puts.:[] ==="Hello world":""
?.
はただの.
なので置き換えます。
'.'..'.'?"".:puts.:[] ==="Hello world":""
もう条件演算子があるのがおわかりだと思うのでif
に直します。
if '.'..'.' "".:puts.:[] ==="Hello world" else "" end
メソッド参照演算子をmethod
メソッドに直します。
if '.'..'.' "".method(:puts).method(:[]) ==="Hello world" else "" end
なるほどですね。
所感
持ち寄りRuby Puzzles面白い。 1文字詰めのクイズに「パズル」と名前がついたのがよい。 ソルバーを使って事前に想定解以外を潰さないと想定外の答えが出る。
Okinawa.rbはここから参加できます。Slackへのリンクもあるので、よかったらどうぞ。