第28回 Ruby勉強会@関西に参加してきました。

毎月恒例の勉強会に参加。

小波先生の「エラトステネスの嵐」が特に好きでした。ちょっとずつ良くなる感じが体感できるストーリー仕立ては見ていて楽しいものです。BenchmarkやProfilerはサボってしまいがちですが、やっぱり重要です。

演習1の解答

英語が苦手なので、メソッド名は適当(^^;

# h:: 身長(m)
# w:: 体重(kg)
def bmi(h, w)
  w / (h ** 2)
end

require 'date'
# year年における1年間の長さ[hours]
def hours_of(year)
  this_y = Date.new(year, 1, 1)
  next_y = Date.new(year+1, 1, 1)
  days = (next_y - this_y)
  (days * 24).to_i
end

# year年から10年間の期間の長さ [minutes]
def minutes_for_ten_years_from(year)
  (0...10).map{ |i|
    hours_of(year+i)*60
  }.inject(0){|sum, mins| sum += mins }.to_i
end

SECONDS_FROM_BIRTHDAY = 9_4300_0000
# date_str に生まれ、それから9_4300_0000秒だけ
# 生きているとした場合の年齢を求める
def age_after_943Msec_from(date_str)
  y,m,d = date_str.split(/[-\/]/).map{ |s| s.to_i }
  kako_date = Date.new(y,m,d)
  mirai = Time.local(y,m,d) + SECONDS_FROM_BIRTHDAY
  mirai_date = Date.new(mirai.year, mirai.month, mirai.day)
  mirai_i = mirai_date.to_s.tr('-/','').to_i
  kako_i = kako_date.to_s.tr('-/','').to_i
  ((mirai_i - kako_i) / 1_00_00).to_i
end

計算100の解答

「40〜80行くらい」という情報は聞いていたので、ボクが書いたら300行くらいかな、と予想し、実際そのくらいでした。相変わらず頼まれもしない機能を盛り込みつつ、テストコードがゼロ・・・(-_-;;

print_sjis_lib.rb
#! ruby -Ku
 
require 'nkf'

# NetBeans では、UTF-8の使用を強制されるため、
# puts, print メソッドを書き換えるモジュールを用意しました。
module PrintSjisLib
  def self.utf82sjis(*args)
    args.flatten.map{ |arg| NKF.nkf('-Ws', arg.to_s) }
  end
  
  alias :print_raw :print
  alias :puts_raw :puts
  
  def utf82sjis(*args)
    PrintSjisLib.utf82sjis(*args)
  end
  
  def print(*args)
    print_raw utf82sjis(*args)
  end
  
  def puts(*args)
    if args.empty?
      puts_raw ''
    else
      puts_raw utf82sjis(*args)
    end
  end
end
calc_hundred.rb
#! ruby -Ku 

require 'print_sjis_lib'
require 'enumerator'
require 'forwardable'

class Array
  def choise_one
    self[rand(size)]
  end
end

module CalcHundred
  # 全問題数
  MAX_N_QUESTIONS = 100
  # 10問ずつ区切る
  N_FRACTION = 10
  # かんたんモード
  MODE_EASY = 1
  # むずかしいモード
  MODE_DIFFICULT = 2
  # モードの一覧
  MODES = [ MODE_EASY, MODE_DIFFICULT ]
  # モードの表示内容
  MODE_NAMES = { MODE_EASY => "#{MODE_EASY}:かんたん",
                 MODE_DIFFICULT => "#{MODE_DIFFICULT}:むずかしい" }
  # 演算子の集合  
  OPS ={ MODE_EASY => %w(+ - *),
         MODE_DIFFICULT => %w(+ - * /) }
  # オペランドの最大値
  MAX_VALUE = { MODE_EASY => 10,
                MODE_DIFFICULT => 100 }
  
  class ExitException < StandardError; end
  
  class Controller
    extend Forwardable
    
    def_delegators :@out, :puts, :print
    def_delegators :@in, :gets
    
    def initialize(max_n_q = nil)
      @in = $stdin
      @out = $stdout
      @out.extend PrintSjisLib
      @n_q = max_n_q || MAX_N_QUESTIONS
      setup
    end
    
    def setup
      @mode = nil
      @elapsed = 0
      @n_correct = 0
      @n_wrong = 0
    end
    
    # mode を呼び出し時に設定することを可能にしています。
    def select_mode(mode = nil)
      puts "計算 #{@n_q} 開始"
      @mode = mode ? mode : get_mode
      puts
    end

    def get_mode
      print "難易度を選択してください。 " +
        MODE_NAMES.values.sort.join(' ') + " > "
      @mode = case @in.gets
      when /\A\s*([#{MODES}])\s*\Z/
        $1.to_i
      when nil
        raise ExitException, "EOFが入力されました。"
      else
        get_mode
      end
    end
    
    def wait_for_ready
      print "#{MODE_NAMES[@mode]} を開始します。 Press Enter > "
      gets; puts
    end
    
    def start
      wait_for_ready
      time_start = Time.now
      srand(time_start.to_i)
      (1..@n_q).each_slice(N_FRACTION) { |poses_q|
        poses_q.each do
          print q = mk_q
          $DEBUG ? sample_answer(q) : answer_by_user(q)
        end
        puts "#{poses_q.last} 問突破" if poses_q.last.modulo(N_FRACTION) == 0
      }
      @elapsed = Time.now - time_start
      puts '', "#{@n_q} 問終了しました。"
    end
    
    def mk_q
      Question.create(@mode, OPS[@mode].choise_one)
    end
    
    def sample_answer(q)
      sleep 0.2
      rand(2) == 0 ? (puts q.answer; correct) : (puts; incorrect)
    end
    
    def answer_by_user(q)
      q.check_answer(gets) ? correct :  incorrect
    end
    
    def correct
      puts "正解"
      @n_correct += 1      
    end
    
    def incorrect
      puts "不正解"
      @n_wrong += 1    
    end
    
    def summary
      puts summary_info
    end
    
    def summary_info
      s = []
      s << "正解  : %d 問" % @n_correct
      s << "不正解: %d 問" % @n_wrong
      s << "タイム: %s" % elapsed_time
      s.join("\n")
    end
    
    def elapsed_time
      "%d 分 %d 秒" % @elapsed.divmod(60)
    end
    
    def exit(e)
      puts e, "終了します"
    end
  end
  
  class Question
    def self.create(mode, op)
      q = self.new(mode, op)
      q.mk_operands
      q
    end
    
    def initialize(mode, op)
      @mode = mode
      @op = op
    end
    
    def mk_operands
      vs = Array.new(2).map{ rand(MAX_VALUE[@mode]) }
      if @op == '/' and (vs[1] == 0 or vs[0].modulo(vs[1]) != 0)
        mk_operands
      else
        @v1, @v2 = vs
      end
    end
    
    def to_s
      "%d %s %d = " % [@v1, @op, @v2]
    end
    
    def check_answer(ans)
      return false if /\A\s*\Z/ =~ ans.to_s
      ans.to_i == answer
    end
    
    def answer
      @ans ||= eval("#{@v1} #{@op} #{@v2}")
    end
  end
    
  def self.main(max = nil)
    begin
      calc = Controller.new(max)
      calc.select_mode 
      calc.start
      calc.summary
    rescue ExitException => e
      calc.exit(e)
    end
  end

end

if $0 == __FILE__
  max = ARGV.shift.to_i
  if max == 0
    CalcHundred.main
  else
    CalcHundred.main(max)
  end
end