Hatena::Grouprubyist

bongoleのRubyを楽しむ日記 このページをアンテナに追加 RSSフィード

Rubyを楽しむ日記

2006-05-21

[][][]続Q1解答 17:35 続Q1解答 - bongoleのRubyを楽しむ日記 を含むブックマーク

途中だったQ1解答の続き。

keys=Hash.new { |h, k|
   puts "Give me #{k.sub(/\A([^:]+):/, "")}:"
   h[$1]=$stdin.gets.chomp
}
puts "", $*[0].split(".")[0].gsub("_", " "),
     IO.read($*[0]).gsub(/\(\(([^)]+)\)\)/) { keys[$1] }

正直一目見ただけではさっぱりなこのコード。

解説読んでもさっぱりでした。

動かしながらpで色々表示させてやっと動きを理解しました。

前提知識:


まずどこでmadlibsのテンプレートを読み込んでいるかというと

"puts "", $*[0].split(".")[0].gsub("_", " "), IO.read($*[0])"

という部分。

$*[0]はARGV[0]と同じなのでテンプレートファイル名の拡張子を取り除き"_"を" "に変換。

でその後のIO.readテンプレートファイルを一気に読み込んでる。

IO.readの後に続く ".gsub(/\(\(([^)]+)\)\)/) { keys[$1] }" の部分が")(("と"))"に囲まれた文字列を実際に置換するコードなんだけどここが厄介。

まず"gsub(/\(\(([^)]+)\)\)/)"で")(("と"))"に囲まれた部分だけを抽出する。

で、そのあとに渡される"{ keys[$1] }" というブロックで抽出した部分を置換してる。

"keys[$1]"の"$1"は"(("と"))"に囲まれた部分が入ってる。

"keys"は、上の行で

keys=Hash.new { |h, k|
   puts "Give me #{k.sub(/\A([^:]+):/, "")}:"
   h[$1]=$stdin.gets.chomp
}

と定義されている。

Hash.newに渡されているブロック引数h, kはそれぞれハッシュ自身(selfと一緒)とキーになっていてハッシュがキーで参照されたときに対応する値が定義されてなかった時に、このブロックが評価される。

'puts "Give me #{k.sub(/\A([^:]+):/, "")}:"'の行は渡されたキーの中に入力値再利用用のタグが付いてないか調べ、ついていたら空文字列に置き換えている。

"h[$1]=$stdin.gets.chomp" の行は入力値再利用用のタグがついていれば"$1"にそのタグが入り、ついていなかった場合はnilが入っている。この行のおかげで入力値の再利用ができている。

正直ここまで書いて何かいてるのかよく分からなくなってきた。。。。。

後は動かしつつpデバッグコードを入れた方が早いと思うのでここで解説終了。

最後に肝になる部分だけあげると

とりあえずこれでQ1は終了。

2006-05-18

[][][] Q1解答 02:20  Q1解答 - bongoleのRubyを楽しむ日記 を含むブックマーク

3つ解答があってまず一つ目。

# use Ruby's standard template engine
require "erb"

# storage for keyed question reuse
$answers = Hash.new

# asks a madlib question and returns an answer
def q_to_a( question )
  question.gsub!(/\s+/, " ")       # normalize spacing
  
  if $answers.include? question    # keyed question
    $answers[question]
  else                             # new question
    key = if question.sub!(/^\s*(.+?)\s*:\s*/, "") then $1 else nil end
    
    print "Give me #{question}:  "
    answer = $stdin.gets.chomp
    
    $answers[key] = answer unless key.nil?
    
    answer
  end
end

# usage
unless ARGV.size == 1 and test(?e, ARGV[0])
  puts "Usage:  #{File.basename($PROGRAM_NAME)} MADLIB_FILE"
  exit  
end

# load Madlib, with title
madlib = "\n#{File.basename(ARGV.first, '.madlib').tr('_', ' ')}\n\n" +
         File.read(ARGV.first)
# convert ((...)) to <%= q_to_a('...') %>
madlib.gsub!(/\(\(\s*(.+?)\s*\)\)/, "<%= q_to_a('\\1') %>")
# run template
ERB.new(madlib).run

'(('と'))'に挟まれた部分をERBの式に置き換えてERBに値の埋め込みをやらせるという方法。

正直ね、"その発想はなかったわ"という言葉を実際言ってしまいそうになる手法。いや、まぁなんでもありだからこれもありだけどさ。。。。

勉強になったのはtestという関数かな。ファイル存在してるかとかファイルのサイズが0でないとかの確認が簡単にできる関数

まぁ知ってると便利だよね。

次、2つ目

# A placeholder in the story for a reused value.
class Replacement
  # Only if we have a replacement for a given token is this class a match.
  def self.parse?( token, replacements )
    if token[0..1] == "((" and replacements.include? token[2..-1]
      new(token[2..-1], replacements)
    else
      false
    end
  end
  
  def initialize( name, replacements )
    @name         = name
    @replacements = replacements
  end
  
  def to_s
    @replacements[@name]
  end
end



# A question for the user, to be replaced with their answer.
class Question
  # If we see a ((, it's a prompt.  
 # Save their answer if a name is given.
  def self.parse?( prompt, replacements )
    if prompt.sub!(/^\(\(/, "")
      prompt, name = prompt.split(":").reverse

      replacements[name] = nil unless name.nil?
      
      new(prompt, name, replacements)
    else
      false
    end
  end
  
  def initialize( prompt, name, replacements )
    @prompt       = prompt
    @name         = name
    @replacements = replacements
  end
  
  def to_s
    print "Enter #{@prompt}:  "
    answer = $stdin.gets.to_s.strip
  
    @replacements[@name] = answer unless @name.nil?
    
    answer
  end
end



# Ordinary prose.
class String
  # Anything is acceptable.
  def self.parse?( token, replacements )
    new(token)
  end
end



# argument parsing
unless ARGV.size == 1 and test(?e, ARGV[0])
  puts "Usage:  #{File.basename($PROGRAM_NAME)} MADLIB_FILE"
  exit  
end
madlib = <<MADLIB

#{File.basename(ARGV.first, ".madlib").tr("_", " ")}

#{File.read(ARGV.first)}
MADLIB

# tokenize input
tokens = madlib.split(/(\(\([^)]+)\)\)/).map do |token|
  token[0..1] == "((" ? token.gsub(/\s+/, " ") : token
end

# identify each part of the story
answers = Hash.new
story   = tokens.map do |token|
  [Replacement, Question, String].inject(false) do |element, kind|
    element = kind.parse?(token, answers) and break element
  end
end

# share the results
puts story.join

これは"apple ((orange)) ((tag:banana))"という文字列をそれぞれ"apple", ")((orange", ")((tag:banana"

と区切ってReplacement型, Question型(, String型に対応させて型ごとに解析メソッドを書くやりかた。

それぞれの型はReplacementは"(("と"))"にはさまれていてすでに入力された文字列を扱う型、

Questionは"(("と"))"にはさまれていてまだ入力されていない文字列を扱う型、

StringはReplacementとQuestion以外の文字列を扱う型ってことみたい。

なんかすごいオブジェクト指向っぽい感じ。

で、勉強になったのは、

"prompt, name = prompt.split(":").reverse"

の部分。 初め"name, prompt = prompt.split(":")"にしても同じじゃねーか、さぼってんのか?と思ったけど

実は意味あって"name, prompt = prompt.split(":")"だと":"でsplitできなかった時promptがnilになって画面になにも出なかったりする。

なんで、"prompt, name = prompt.split(":").reverse"として":"でsplitできる場合もできない場合もかならずpromptには何かが入ってるようにしてある。

いや、ぶっちゃけわかりづらいんだけど、なんかハカーって感じでちょっと感動した。

あと、勉強になったのは

"element = kind.parse?(token, answers) and break element"のbreakの使い方。

rubyのbreakはバージョン1.7から引数がとれて引数をとった場合その引数戻り値として返せるらしい。

なかなかcoolですな。

で、最後3つ目

keys=Hash.new { |h, k|
   puts "Give me #{k.sub(/\A([^:]+):/, "")}:"
   h[$1]=$stdin.gets.chomp
}
puts "", $*[0].split(".")[0].gsub("_", " "),
     IO.read($*[0]).gsub(/\(\(([^)]+)\)\)/) { keys[$1] }

んーかなり神がかったコードで説明もむずいんで明日やろう。

続く

2006-05-17

[][][]回答してみる 回答してみる - bongoleのRubyを楽しむ日記 を含むブックマーク

昨日書いた問題をやってみる。

何も考えずただただ書きたいように書いてみた。

def usage
    puts "usage: #{$PROGRAM_NAME} mad_libs_templage"
    exit
end

usage unless ARGV.size == 1

result  =  String.new
reuse_map = Hash.new
stack = Array.new

open(ARGV.shift).each_line do |line|
    line.split(/(\(\(|\)\))/).each do |token|
        if token == '))'
            prompt = stack.pop
            stack.pop # '(('を空読み
            raise "Syntax error near by '#{prompt}'" unless stack.empty?

            if prompt.index(/:/)
                tag, new_prompt = prompt.split(/:/)
                print "#{new_prompt} :"
                s = gets.chomp
                result << s
                reuse_map[tag] = s
            else
                s = reuse_map[prompt]

                unless s
                    print "#{prompt} :"
                    s = gets.chomp
                end

                result << s
            end

        elsif token == '((' or not stack.empty?
            stack.push token
        else
            result << token
        end
    end
end

print result

昨日の例題をファイルに書いてこのスクリプトに読ませると、色々聞いてくるので答えてあげると最後に'(('と'))'の部分を埋めて表示してくれる。

工夫したところは特にない、今は反省しているw

まぁあれだ書いてて思ったのは今更言うまでもないが、Rubyは便利ってこと。Javaで書いてたらこんなに簡単に作れなかったなきっと。

さてさて明日は解答を見てみよう。

どんなテクニックが使われているか楽しみ:-)

続く

2006-05-16

[][][]Quiz1: Mad Libs Quiz1: Mad Libs - bongoleのRubyを楽しむ日記 を含むブックマーク

1問目はMad Libsというアメリカのお子様が大好きなゲームを作ろうというお題。

まぁといっても僕はアメリカのお子様であったことがないのでMad Libsがどんなゲームかさっぱりわからん。

わからんときはググれということで、偉大なるGoogle先生に聞いてみたところ

【インフォシーク】Infoseek : 楽天が運営するポータルサイトより

文章を隠しておいていくつかの単語の品詞だけを示し,参加者適当な単語を入れたときに意外な文章ができあがるのを楽しむゲーム日本の「いつどこで何をした」とは異なるが,意外な取り合わせを楽しむ趣旨は同じと言える.

なるほど。なにが面白いのかわからないけど、どんなゲームか大体わかった。

お題のほうのMad Libsゲームの例文を見ると

I had a ((an adjective)) sandwich for lunch today. 
It dripped all over my ((a body part)) and ((a noun)).

と書いてあって"an adjective"や"a body part"をユーザーに入力させろと書いてある。

ふむふむ。

で、他にも

Our favorite language is ((gem:a gemstone)). 
We think ((gem)) is better than ((a gemstone)).

みたいな風にも書けるようにして、"gem:"のようにラベルが付いたものは再利用できるようにしろと書いてある。

この例の場合"a gemstone"で入力された文字列を、後ろにでてくる"gem"でも使しろと。

で、"an adjective"とか"a body part"は必ず"(("と"))"に挟まれてるってことでいいみたい。

最後に、インターフェースは問わないので、CUIなりGUIなりCGIなり好きなように作れと。

とりあえず、問題としてはこんなもん。

まぁ要するにテンプレート食って"((" と"))"に挟まれた部分をユーザーに入力させて置換するプログラムを作れということですな。

さてさて、どういう風に作ろうかな。

続く