教えながら学ぶRuby: それでも僕は君をイテレータと呼ぶ
2007.10.04
前回の「イテレータに片思い」、たくさんのフィードバックをいただき、とても良い勉強になっている。一人でこつこつと勉強するよりも、励みになるしずっと楽しい。
ちなみに、1問目の問題だが、
def hello(to, *mesg) print "Hello, ", to, ".\n"print "-- message -- \n"
for m in mesg
print m, "\n"
end
end
これに関しては、予想した通り、ほとんどの人が私と同じく、
def hello(to, *mesg) print "Hello, ", to, ".\n"print "-- message -- \n"
mesg.each { |m| print m, "\n" }
end
という答えにたどりついた({..} のかわりに do..end でも良い)。圧倒的に読みやすいし、とにかく美しい。「プログラムはできるだけ読みやすくあるべき」というポリシーの私としては、これだけで「Rubyに一目惚れ」である。生みの親のまつもとゆきひろ氏の「読みやすいコード」への思いが伝わってくる。
ところが、2問目の問題、
def fact(n) return 1 if n==0f=1
while n>0
f*=n
n -= 1
end
return f
end
に関しては、私の用意していた答えは、
def fact(n)f=1
(1..n).each { |i| f*=i }
return f
end
なのだが、他にも色々な書き方があるようで、
def fact(n)f=1
n.downto 1 do
|i| f*=i
end
return f
end
などのバラエティも寄せられ、「同じことが色々な方法ができるというのはあまり美しくないな」ともう二回目のデートから相手のあらが見えてしまうしまつ。そんなときに、
def fact(n)(1..n).inject(1) {|r, i| r*i}
end
という私がまだ学んでいない inject なるもので f=1 を排除したものが寄せられ、「うむむ」と思わせるあたりが男心をくすぐるというか何と言うか。
ちなみに、最近はこのイテレータのことはイテレータとは呼ばずにブロックなどと呼ぶらしいが、私としては生みの親の気持ちを尊重してイテレータと呼び続けたい。
1回しか呼ばれなかろうと 時には一度も呼ばれなかろうと それでも君はイテレータ
>1回しか呼ばれなかろうと
>時には一度も呼ばれなかろうと
>それでも君はイテレータ
まったくイテレーションしないバージョンもどうぞ。
def fact(n)
proc{|f|g=proc{|q|proc{|m|f[q[q]][m]}};g[g]}[proc{|h|proc{|n|n<2?1:n*h[n-1]}}][n]
end
これで片思いの気持ちもさめる?
Posted by: kazutanaka | 2007.10.04 at 21:56
自分もいまRubyの勉強を始めたところです。
質問なのですが,以下のような問題はイテレータだけで解けるでしょうか。
配列Aと配列Bがあり,配列Aには各項目に文字列が,配列Bには配列Aの項目の一部が入っています。
このとき,配列Aにおける順序と配列Bにおける順序は一致しています(配列Aの小さいインデックスに入っている方が,配列Bでも小さいインデックスになる)。配列Aにおいてはいくつか同じ文字列の項目があるので,マッチングを一意に決めるにはこの条件が必要です(また,この条件からハッシュは利用できません)。
この条件で配列Aのどの項目と配列Bの項目がマッチするかを調べる方法が問題です。
例
A…りんご,ばなな,みかん,りんご,かき,ぶどう
B…ばなな,りんご,ぶどう
とすると2番目,4番目,6番目にマッチする
旧来型プログラミング的考え方だとどこまで探したかインデックスをどこかの変数に入れてやっていくと思いますが,あまりきれいではないですよね。もしこれがイテレータできれいに解けるのなら感動すると思います。
Posted by: Andy | 2007.10.04 at 23:23
ちなみに自分で解いたのは以下の方法。イテレータは使ってますが,「だからどうよ」って感じです。
a = Array["りんご","ばなな","みかん","りんご","かき","ぶどう"]
b = Array["ばなな","りんご","ぶどう"]
results = Array.new # 結果格納用配列
i = 0 # a 配列の中の調べているインデックス
max = a.size
b.each do |fruit|
(i..max-1).each do |j|
if a[j] == fruit
results[j] = 1
i = j
break
end
end
end
# 結果チェック
(0..max-1).each do |i|
if results[i] == 1
puts a[i]
end
end
Posted by: Andy | 2007.10.05 at 00:56
>イテレータだけで解けるでしょうか。
共通要素で重複除去するならアンドで一発。
puts a & b
重複除去しないならイテレータで。
puts a.select{|x| b.include?(x)}
あまりRubyっぽくなかったので直してみた。
プレビューしたらアンドのフォントが
変だったけどなんでだろう。
Posted by: kazutanaka | 2007.10.05 at 01:47
同じ処理を色々な書き方ができるのがRubyの特徴の一つなので、そこは慣れて頂かないと・・。
ちなみに1問目の
mesg.each { |m| print m, "\n" }
は自分なら
puts mesg.join("\n")
かなぁ。
Posted by: nido | 2007.10.05 at 16:21
>> Andyさん
こんなんでどうでしょ?
a = Array["りんご","ばなな","みかん","りんご","かき","ぶどう"]
b = Array["ばなな","りんご","ぶどう"]
results=[]
b.inject(0){|i,e| i+=a[i..-1].index(e);results[i]=1;i}
# 結果チェック
a.each_with_index {|e,i| puts e if results[i]==1}
Posted by: nido | 2007.10.05 at 16:52
kazutanakaさん,nidoさん,ありがとうございました。
Posted by: Andy | 2007.10.07 at 11:32
Rubyの生みの親の名前は「まつもとゆきひろ」さんです...
Posted by: 『ばぶる』 | 2007.10.08 at 19:50
>Rubyの生みの親の名前は「まつもとゆきひろ」さんです...
ご指摘ありがとうございます。修正しました。
Posted by: satoshi | 2007.10.08 at 20:32
美しい詩歌が初学者に対しては不明瞭であったりするので
読みやすさ(理解のしやすさ)と美しさはイコールではない、と考えます。
また、可読性(読みやすさ)と記述性(書きやすさ)とは区別した方がよいと思います。
each メソッドはどちらかというと記述性を向上させています。
たとえば、Ruby を知らないけど英語を知っている人は
下の二つのソースのうち、上の方を読みやすく感じるでしょう。
ずるして、for を each に書き換えてますが、下の方は
ループ変数を | | で囲むとこが自然言語からすると特殊です。
each m in mesg
print m, "\n"
end
mesg.each {
|m| print m, "\n"
}
個人的には、puts mesg.join("\n") は
上のものよりは読みにくいけど美しいかと。
Posted by: 田辺 | 2007.10.16 at 02:20