RubyのProcは「関数」としてブロックを取れるか?
結論。驚くべきことに、Procオブジェクトは「関数」と見做してブロックを取るような使い方ができる。
foo = ->(&bk){
bk.call.upcase
}
puts foo.() { "nyao" } #=>NYAO
さらに、enum_forやto_enumを使ってメソッドを Enumerator化することができるように、ブロックを取る ProcオブジェクトもEnumerator::Yielder#to_procを使って Enumerator化することができる。
def foo (1..10).each { yield(_1) } end enum_for(:foo).map { _1 * 2 } #=>[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
foo = ->(&bk){
(1..10).each { bk.call(_1) }
}
Enumerator.new { |y| foo.(&y) }.map { _1 * 2 }
#=>[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
define_method と block_given?(Ruby)
Ruby でメソッドにブロックが与えられているかを確認する、Kernel#block_given?というメソッドがある。
def foo p block_given? end foo #=>false foo {} #=>true
これが、メソッドをModule#define_methodで定義した場合、使えない。
define_method(:foo) do p block_given? end foo #=>false foo {} #=>false
というか、そもそもこの場合、yieldが使えないので、ブロックは Procオブジェクトで与えてやることになる。
define_method(:foo) do yield end #=>Invalid yield (SyntaxError)
define_method(:foo) do |&bk| bk.call end
では、こうするとして、block_given?はどういう手段で代替するか。
それは、ブロックが与えられていない場合、bkがnilになることを使えば、実現できそうである。
define_method(:foo) do |&bk| p bk #ブロックが与えられていなければnilになる bk.call if bk end foo #=>nil foo {} #=>#<Proc:0x00007f1464fe6508>
Ruby と関数型プログラミングについてのメモ(1)
jp.quora.com
まつもとゆきひろさんは、Ruby を「関数型言語と呼ぶのにはだいぶ抵抗があります」といっておられる。これでもう結論は出たようなものだが、それでも、Ruby にはだいぶ関数型言語の考え方が取り入れられているのも事実だ。それをちょっとだけ考えてみる。
Array, Enumerable, Enumerator
配列a = [1, 2, 3]の各要素を 2倍した新しい配列new_aが欲しい。これを「手続き型」プログラミングを使ってやるとどうなるか。
a = [1, 2, 3] new_a = [] for i in a new_a << i * 2 end new_a #=>[2, 4, 6]
例えば、こんな感じ?
でも、たぶん Ruby初心者でも、あんまりこんな風には書かないよね。Array#mapを使って、こう書くと思う。
a = [1, 2, 3] new_a = a.map { |i| i * 2 } #=>[2, 4, 6]
このmapが既に関数型プログラミングの考え方を使っている。この場合、ブロックが「関数」の役割を果たしている。そもそも Ruby でブロックを使うこと自体、関数型プログラミングっぽいのだ。ここでのブロックは、簡単に「無名関数」を使っていることになる。
EnumerableやEnumeratorは繰り返しを抽象化している。例えば、アルファベットの小文字だけで出来ている文字列を、すべて大文字にしたいとする。もちろん、String#upcaseを使えば一発だが、それに頼らず、仮にこんなふうに自力でやってみる。
str = "ruby" e = str.each_byte e.map { |b| (b - 32).chr }.join #=>"RUBY"
このeのクラスがEnumeratorで、each_byteにより(繰り返しのように)1文字ずつ小文字を切り出す。EnumeratorはEnumerableを継承しているのでEnumerable#mapが使えるのである。
EnumerableやEnumeratorを駆使したプログラミングはとても Ruby らしく、関数型プログラミングっぽい。
ブロックを敢て使わない 関数型Rubyプログラミング
yuroyoro.hatenablog.com
ブロックは関数型プログラミングをお手軽に実現する、すばらしい発明である。それだけで、可読性の高い、しかも強力なプログラミングが可能になる。
しかし、ここからは趣味の領域になるが、敢て(あまり)ブロックを使わず、もっと関数型プログラミングを推し進めることもできる。ProcやMethodオブジェクトを関数のように使ったプログラミングである。
例えば配列a = [1, 2, 3]の各要素を 2倍して、さらに各々 10を足した配列が得たいとする。もちろん、
a = [1, 2, 3] new_a = a.map { |i| i * 2 + 10 } #=>[12, 14, 16]
でいいのだが、あえて演算をバラしてみる。
a = [1, 2, 3] new_a = a.map { |i| i * 2 }.map { |i| i + 10 } #=>[12, 14, 16]
しかしこれを、Proc(あるいはlambda)を関数のように使って、ブロックの代わりにしてみる。
a = [1, 2, 3] double = ->(i) { i * 2 } add = ->(i) { i + 10 } new_a = a.map(&double).map(&add) #=>[12, 14, 16]
mapを重ねる代わりに、Proc#>>を使って「関数合成」してもよい。
new_a = a.map(&double >> add) #=>[12, 14, 16]
関数合成の順番を入れ替えれば、10を足してから 2倍ということになる。
new_a = a.map(&add >> double) #=>[22, 24, 26]
Methodオブジェクト
じつはメソッドも、Procのように「関数化」できる。これはかなり趣味の領域に入ってくるが。
似たことを、Object#methodを使ってやってみる。
a = [1, 2, 3] def double(i) = i * 2 def add(i) = i + 10 new_a = a.map(&method(:double) >> method(:add)) #=>[12, 14, 16]
一行def の文法を使った。見てのとおり、かなり趣味的である。
methodメソッドの名前が長ければ、適当にそのエイリアスを定義してもよい。
Object.alias_method(:■, :method) new_a = a.map(&■(:double) >> ■(:add)) #=>[12, 14, 16]
こうなるともうよくわからない笑。
並んだタイルの塗り方は何通りかというパズルを解く
問題
9個の白マスが横一列に並んでいます。このうち、いくつかの白マスを黒く塗ります。ただし、少なくとも一つは黒く塗り、また、黒マスが連続することはありません。
このとき、塗り方は全部で何通りあるでしょう。
Ruby で解いてみた
index: 左から何マス目か
pred: 左隣りの色(左端の場合は nil)
再帰を使っています。
def paint(index, pred = nil) return 1 if index == 9 s = 0 if index.zero? || pred == :white s += paint(index + 1, :white) s += paint(index + 1, :black) else s += paint(index + 1, :white) end s end puts paint(0) - 1 #=>88
答えは88通りです。最後に 1 を引いているのは、これだとすべて白マスで黒く塗らない場合が一つ含まれてしまうからです。
メモ化
100マスだとフリーズするので、メモ化してみます。
def paint(index, pred = nil) return 1 if index == 100 return @memo[[index, pred]] if @memo.has_key?([index, pred]) s = 0 if index.zero? || pred == :white s += paint(index + 1, :white) s += paint(index + 1, :black) else s += paint(index + 1, :white) end @memo[[index, pred]] = s end @memo = {} puts paint(0) - 1 #=>927372692193078999175
かけっこのパズル(Ruby)
問題
一郎、二郎、三郎の三人で駆けっこをして、その結果を次のように言っています。
一郎:「僕は一番じゃない」
二郎:「僕は一番だ」
三郎:「僕は二番だ」
三人のなかで一人だけウソをついています。それは誰でしょう?
Ruby で解いてみた
総当りで解いています。
0, 1, 2 でそれぞれ一郎、二郎、三郎を表しています。
names = %W(一郎 二郎 三郎) (0..2).each do |usotsuki| #嘘つきを選びます table = [-1, 1, 2] #与えられた条件(否定は負にします) #嘘をつかせます table[usotsuki] = -table[usotsuki] #可能性のある順位を配列にします tmp = table.map { _1 < 0 ? [1, 2, 3] - [-_1] : [_1] } #順位を総当りでまわして判定します [1, 2, 3].permutation do |candidate| if candidate.zip(tmp).all? { |c, ary| ary.include?(c) } puts "嘘つきは#{names[usotsuki]}です。" str = names.zip(candidate).map { |n, c| "#{n}が#{c}位" }.join("、") puts "ちなみに順位は#{str}です。" end end end
結果
嘘つきは三郎です。 ちなみに順位は一郎が2位、二郎が1位、三郎が3位です。
辞書順で何番目か(Ruby)
辞書順(lexicographical order)に並べて何番目か、あるいはn番目のものは何か。
要素に重複がないとして考える。数列 (1, 2, .... , n) の並べ替えとして解こう。
数列 (a1, .. , an) は辞書順で何番目か。
def number_in_lex_order(ary) n = ary.size return 1 if n <= 1 idx = ary.sort.bsearch_index { _1 >= ary[0] } (1..n - 1).inject(:*) * idx + number_in_lex_order(ary[1..-1]) end number_in_lex_order([2, 1, 5, 3, 4]) #=>29
ここでは、数列の左端の数字が、数列を昇順にソートした中で何番目に来るかがわからなければならない。それを i (=idx+1) とすると、そこまでで (n - 1)! * (i - 1) だけ既に(一桁少ない部分)数列が並んでいることになる(ただし n は ary の桁数)。
あとは左端の一桁を落として同じことを(再帰で)求め、すべての和をとってやれば求まる。
文字列などならば、ハッシュで数列に変換するテーブルを作ってやればよい。
str = "MATH" h = "AHMT".each_char.with_index(1).to_h number_in_lex_order(str.each_char.map { h[_1] }) #=>14
もっとも、文字列ならば Ruby の柔軟性(?)のおかげで、
number_in_lex_order("MATH".chars) #=>14
で求まってしまうのだが。
これの逆、つまり辞書順でn番目の数列はどうなるか。この場合、数列の長さ(size)を与えることが必要である。
def nth_sequence_in_lex_order(n, size) i = 1 table = (1..size - 1).map { i *= _1 } order = (1..size).to_a ans = [] n -= 1 size.pred.times do factorial = table.pop i = n / factorial n -= i * factorial ans << (a = order[i]) order.delete(a) break if n <= 0 end ans.concat(order) end
やってみる。
nth_sequence_in_lex_order(29, 5) #=>[2, 1, 5, 3, 4]
よさそうだ。
上と同様に、文字列でもやってみる。これも、ハッシュでテーブルを作り、Hash#invert する。
h = "AHMT".each_char.with_index(1).to_h.invert nth_sequence_in_lex_order(14, 4).map { h[_1] }.join #=>"MATH"
これもよさそうだ。
なお、いずれも不正入力には対応していない。
約数を求める(Ruby)
prime ライブラリを使う。結果は順不同。
require "prime" def divisors(n) result = [1] doit = ->(pd, acc) { return if pd.empty? x, *xs = pd (0..x[1]).each do |i| e = acc * x[0] ** i result << e doit.(xs, e) end } doit.(n.prime_division, 1) result.uniq end p divisors(24) #=>[1, 3, 2, 6, 4, 12, 8, 24]