Okinawa.rb Ruby Puzzles 2019/5/15 hanachinパズル解説

RubyKaigi 2019のCookpad Ruby Puzzlesがとてもおもしろくてお返しパズルをつくってみました。パク^H^Hリスペクト。

ここから先、余白を空けたりしてから解答と解説を載せていきます。





































































































1問目

事前にSolverを通していなかったので潰せていない解答が2つありました。 まず想定解から説明します。

まずパズルの動作です。case inx1.010101を代入し、^xxの中身とマッチさせています。 マッチしなかった場合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

8進整数

https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#num

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

想定外の解

xxを比較したらマッチするのは当たり前なので、xxを比較しないようにすれば解けます。

xの前に単項演算子!-を付け加えると以下のような動作になり、マッチしなくなります。2

  1. xに値を代入
  2. !xや-xを評価
  3. !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

来歴紹介

使わないでと言われると使いたくなるので使った。

ところでこれブロックをわたしているように見えるじゃないですか?

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です。

to_proc -> Proc[permalink][rdoc]

self に対応する Proc オブジェクトを返します。

h = {1 => 10, 2 => 20, 3 => 30}
[1, 2, 3].map(&h) # => [10, 20, 30]

https://docs.ruby-lang.org/ja/latest/method/Hash/i/to_proc.html

ということで答えは「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へのリンクもあるので、よかったらどうぞ。

okinawarb.doorkeeper.jp


  1. elseでやってもよかったけどコメントアウトとか2文字解がすぐできそうな感じが微妙だったのでやめた

  2. 代入したあとに単項演算子が評価されるの気づいていなかった。ふだんここで代入しつつ単項演算子呼ぶことないので気づけなかった、なぜか負けた気分!

いいなと思ったらKyashでお金を下さい
20191128011151
GitHubスポンサーも受け付けています
https://github.com/sponsors/hanachin/