Rubyで配列に対して重複を許す順列を列挙するメソッドを再帰で実装したが標準にもうありました

要するにこういうのの出力する要素数によらない版を作りたかった
(出力する要素数だけブロックを入れ子にしないといけないので)

def naive_enumerate array
  array.each do |a_1|
    array.each do |a_2|
      array.each do |a_3|
        yield [a_1, a_2, a_3]
      end
    end
  end
end

arr = [1, 3, 5]
naive_enumerate arr do |a|
  p a
end

出力結果はこんなかんじ
[1, 1, 1]
[1, 1, 3]
[1, 1, 5]
[1, 3, 1]
[1, 3, 3]
[1, 3, 5]
[1, 5, 1]
[1, 5, 3]
[1, 5, 5]
[3, 1, 1]
[3, 1, 3]
[3, 1, 5]
[3, 3, 1]
[3, 3, 3]
[3, 3, 5]
[3, 5, 1]
[3, 5, 3]
[3, 5, 5]
[5, 1, 1]
[5, 1, 3]
[5, 1, 5]
[5, 3, 1]
[5, 3, 3]
[5, 3, 5]
[5, 5, 1]
[5, 5, 3]
[5, 5, 5]
で、こういう実装をしてみたが

def enumerate_error array, n, buf=[]
  if n == 0
     yield buf
  else
    array.each do |a|
      enumerate_error array, n-1, buf + [a]
    end
  end
end

arr = [1, 3, 5]
enumerate_error arr, 3 do |a|
  p a
end

`enumerate_error': no block given (LocalJumpError)というエラー発生
再帰的に呼び出してる所がブロック付きメソッド呼び出しになってないからですね
ブロックに渡された要素をまんま渡すよう実装

def enumerate array, n, buf=[]
  if n == 0
    yield buf
  else
    array.each do |a|
      enumerate(array, n-1, buf + [a]) {|b| yield b}
    end
  end
end

arr = [1, 3, 5]
enumerate arr, 3 do |a|
  p a
end

で、無事完成したが、Ruby 1.9.2 からもう標準で実装されていたようです

arr = [1, 3, 5]
arr.repeated_permutation(3) do |a|
  p a
end

Array#permutation (Ruby 1.8.7から)
Array#combination (Ruby 1.8.7から)
Array#repeated_permutation (Ruby 1.9.2から)
Array#repeated_combination (Ruby 1.9.2から)
といたれり尽くせりのようです
http://ref.xaio.jp/ruby/classes/array

ランニングをしながら学んだ事を頭の中で復習しよう

兄pod(兄にipodを譲渡する事)のせいでランニング中にpodcastが聞けなくなったので、どうにもヒマだと思ってたんですが良い方法を思いつきました。今日学んだ事を思い出して復習すればいいんです。
受験期にはよく帰り道の電車の中で勉強した事を思い出して復習してましたが、最近その習慣がなくなっていました。自分で実践してみて思う感想なのですが、頭の中で思い出すというのはとても良い勉強になります。
まず、自分で良く理解して整理されていないとうまく思い出す事が出来ません。これが起こるたびに「理解してねぇじゃんww」と反省し、時にはどうしても気になって参考書を調べなおすという事もよくやりました。
それと、何回も思い出して反芻するうちにだんだんとそのスピードが速くなり、最終的には一瞬で体系だった知識を思い出せるようになります。まぁ数日たてば出来なくなりますが、かなり長期に渡って知識を記憶しておく事が出来ます。



丁寧語やめよう、なんか肌に合わねぇ。
で、電車の帰り道とかだと疲労困憊してて、今まで勉強してたのに気が休まらねぇとか考えてしまうけど、ランニング中だとむしろ逆の思考になる。
出来る限りランニングとは別の事を考えて、気を紛らわしたいと思うようになる。
うまく気を紛らわすためにipod使ってたけど、必要なかったんや!これでランニングの習慣も続けられそうだし、勉強も捗りそうだ。
皆さんも是非お試しあれ。

さらに赤面ソースコードをリファクタリングしてみた

前回のソースコードにアドバイスをもらったのでさらにリファクタリングしてみました。
このリファクタリングする前にスクリプト走らせる前のyes/noのチェッカやら付けてクラスが肥大化してたのでMechanizeの部分だけをクラス抽出してます。

#encoding:utf-8
require 'rubygems'
require 'mechanize'
require 'kconv'

class IntentLotteryUnit
  INTENT_URL = 'http://www.hyogo-park.or.jp/yoyaku/intention/auth.asp?ch=0'
  LOGOUT_URL = "http://www.hyogo-park.or.jp/yoyaku/kaiin/logout.asp"

  def initialize
    @agent = Mechanize.new
  end

  def check_unit(id, pass, name, date, range_time, out_file)
    access
    login(id, pass)
    write_down_already_verified(id, pass, name, out_file)
    verify_intent(id, pass, name, date, range_time, out_file)
    logout
  end

  private
  def access
    @agent.get(INTENT_URL)
  end

  def login(id, pass)
    @agent.page.form_with(:name => 'form1') do |f|
      f.field_with(:name => 'mem_number').value = id
      f.field_with(:name => 'mem_password').value = pass
      f.click_button(f.button_with(:value => ' 次  へ '))
    end
  end

  def write_down_already_verified(id, pass, name, out_file)
    @agent.page.search('tr/td/table[@width="400"]')[0..50].each do |p|
      if /予約は承認されました/ =~ p.inner_text
        date_time = capture_date_time(p)
        write_down(date_time, id, pass, name, "予約済み\n", out_file)
      end
    end
  end

  def verify_intent(id, pass, name, date, range_time, out_file)
    approvals = @agent.page.links_with(:text => '予約承認確認')
    approvals.each do |apr|
      apr.click
      date_time = capture_date_time_in_aprove
      write_down(date_time, id, pass, name, "\n", out_file)
      if date == date_time[0] && range_time == date_time[1]
        @agent.page.form_with(:name => 'form1') do |f|
          f.submit(f.button_with(:name => 'submi2'))
        end
      end
    end
  end

  def logout
    @agent.page.link_with(:href => LOGOUT_URL).click
  end

  def capture_date_time_in_aprove
    p = @agent.page.at('form/table[@width="400"]/tr/td/table[@width="400"]')
    capture_date_time(p)
  end

  def capture_date_time(p)
    date_time = []
    date_time << $1 if /([0-9]*年[0-9]*月[0-9]*日)/ =~ p.inner_text
    date_time << $1 if /([0-9]*時〜[0-9]*時)/ =~ p.inner_text
  end

  def write_down(date_time, id, pass, name, eol, out_file)
    line = [date_time, id, pass, name, eol].join(',')
    puts(line)
    out_file.print(line)
  end
end

友人にコード見てもらってすごいコードがすっきりしてうれしかったけど、少しくやしい。
verify_intent()と言いつつも意思確認をまだ行っていない日付を書き下す処理も入ってるのでここらへんもどげんかせんといかん。
追記:RubyのMechanizeはkitamomongaさんの記事
RubyのMechanizeを解説 for 1.0.0で学びました。そろそろお礼をせんといかん。

前回の赤面ソースコードをリファクタリングしてみました。

学校の課題にリファクタリングがあったのと、いいかげんこれはひどいという事で前回の赤面ソースコードリファクタリングしてみました。
とりあえずクラス化、バカ長いメソッドcheck_unit()を細分化したのと、ページの文字列検索をcapture_date_time()に、文字列の標準出力とファイル出力をwrite_down()へ共通化しました。
Clean Code アジャイルソフトウェア達人の技に「関数は第一に短く、第二にもっと短く」ってあったけど、なるほど頭スッキリする。

#encoding:utf-8
require 'rubygems'
require 'mechanize'
require 'kconv'

class IntentLottery

  def initialize
    @agent = Mechanize.new
  end

  def check_only(filename_id, filename_out)
    out_file = open(filename_out,"w")
    open(filename_id).each_line do |line|
      id, password, name = line.split(/,/)
      check_unit(id,password,name,'tekitou','tekitou2',out_file)
    end
    out_file.close
  end

  def check_and_regist(filename_intent, filename_finish)
    finish_file = open(filename_finish,"w")
    open(filename_intent).each_line do |line|
      if(/----/ =~ line) then
        puts ("next day")
        next
      end
      date,range_time,id,password,name = line.split(/,/)
      check_unit(id,password,name,date,range_time,finish_file)
    end
    finish_file.close
  end

  private
  def check_unit(id, password, name, date, range_time, out_file)
    @agent.get('http://www.hyogo-park.or.jp/yoyaku/intention/auth.asp?ch=0')
    authenticate(id, password)
    write_down_already_verified(id, password, name, out_file)
    verify_intent(id, password, name, date, range_time, out_file)
    @agent.page.link_with(:href =>  "http://www.hyogo-park.or.jp/yoyaku/kaiin/logout.asp").click
  end

  private
  def authenticate(id, password)
    @agent.page.form_with(:name => 'form1'){|f|
      f.field_with(:name => 'mem_number').value =id
      f.field_with(:name => 'mem_password').value = password
      f.click_button(f.button_with(:value => '&nbsp;次&nbsp;&nbsp;へ&nbsp;'))
    }
  end

  private
  def write_down_already_verified(id, password, name, out_file)
    @agent.page.search('tr/td/table[@width="400"]')[0..50].each do |p|
      if(/(予約は承認されました。)/ =~ p.inner_text)then
        date_time = capture_date_time(p)
        write_down(date_time, id, password, name, "予約済み\n", out_file)
      end
    end
  end

  private
  def verify_intent(id, password, name, date, range_time, out_file)
    approvals = @agent.page.links_with(:text => '予約承認確認')
    approvals.each do |apr|
      apr.click
      date_time = capture_date_time_in_aprove
      write_down(date_time, id, password, name, "\n", out_file)
      if (date == date_time[0] && range_time == date_time[1]) then
        @agent.page.form_with(:name => 'form1'){|f|
          f.submit(f.button_with(:name => 'submi2'))
        }
      end
    end
  end

  private
  def capture_date_time_in_aprove
    arr = Array.new
    p = @agent.page.at('form/table[@width="400"]/tr/td/table[@width="400"]')
    arr = capture_date_time(p)
    return arr
  end

  private
  def capture_date_time(p)
    arr = Array.new
    if(/([0-9]*年[0-9]*月[0-9]*日)/ =~ p.inner_text) then
      arr << $1
    end
    if(/([0-9]*時〜[0-9]*時)/ =~ p.inner_text) then
      arr << $1
    end
    return arr
  end

  private
  def write_down(date_time, id, password, name, eol, out_file)
    line = Array.new
    line << date_time
    line << [id, password, name, eol]
    puts(line.join(','))
    out_file.print(line.join(','))
  end
end

シンタックスハイライト反映されないし、タブ8文字て…どうなってやがる…!?
追記:シンタックスハイライトに対応されたのことなので編集して更新、タブ8文字どうすればいいんだろ?
追記:タブをスペース2文字に置換しました

rubyのmechanizeライブラリを用いたとあるテニスコートのコート予約自動化スクリプト(の一部)赤面ソースコード公開

大学のIT技術講演会で株式会社はてなの人に触発され、株式会社はてなの回し者である友人 @y_uuki に招待されるがまま
ブログを始めました。
はてな記法モードというものでソースコードを綺麗に表示出来るとのことなのでとりあえず
rubyのMechanizeライブラリを用いたとあるテニスコートのコート予約自動化スクリプト
(の一部、テニスコートの抽選予約に選ばれたものに対して「予約します」という意思確認を行う処理の自動化)のソースコードを公開します。
因にputs("Usage:intentioncheck.rb input/id.txt output/filename_out")となっていますが、読み込むファイルid.txtは
"ID,パスワード,名前(改行)"を80人分書いてあります。
かなり汚いコードなので書き方の添削をしていただけるとありがたいです。

# encoding: utf-8
require 'rubygems'
require 'mechanize'
require 'kconv'

def check_unit(id,password,name,date,range_time,out_file)
	agent = Mechanize.new()
	#if(agent.get('http://www.hyogo-park.or.jp/yoyaku/intention/auth.asp?ch=0'))then
	#if(agent.get('http://www.hyogo-park.or.jp/yoyaku/intention/auth.asp?ch=0'))then
	#	$stderr.print("Error:Term is over\n")
	#	exit
	#end
	agent.get('http://www.hyogo-park.or.jp/yoyaku/intention/auth.asp?ch=0')
	agent.page.form_with(:name => 'form1'){|f|
		f.field_with(:name => 'mem_number').value =id
		f.field_with(:name => 'mem_password').value = password
		f.click_button(f.button_with(:value => '&nbsp;次&nbsp;&nbsp;へ&nbsp;'))
	}
	#http://www.hyogo-park.or.jp/yoyaku/intention/intention_check.asp
	agent.page.search('tr/td/table[@width="400"]')[0..50].each do |p|
		if(/(予約は承認されました。)/ =~ p.inner_text)then
			if(/([0-9]*年[0-9]*月[0-9]*日)/ =~ p.inner_text) then
				print($1,(','))
				#puts p.inner_text
				out_file.print($1,(','))
			end
			if(/([0-9]*時〜[0-9]*時)/ =~ p.inner_text) then
				print($1,(','))
				#puts p.inner_text
				out_file.print($1,(','),id,(','),password,(','),name,(','))
			end
			puts("予約済み\n")
			out_file.print("予約済み\n")
		end
	end
	approvals = agent.page.links_with(:text => '予約承認確認')
	approvals.each do |apr|
		apr.click
		s_date = 'init'
		agent.page.search('div')[0..50].each do |p|
			if(/([0-9]*年[0-9]*月[0-9]*日)/ =~ p.inner_text) then
				print($1,(','))
				s_date = $1 #$&.clone
				#puts p.inner_text
				out_file.print($1,(','))
			end
			if(/時〜/ =~ p.inner_text) then
				puts p.inner_text
				search_time = p.inner_text
				if(date == s_date && range_time == search_time)then
					puts('match so resist')
					agent.page.form_with(:name => 'form1'){|f|
					f.submit(f.button_with(:name => 'submi2'))
					}
					puts agent.page.uri
					puts('resitered')
				end
				out_file.print(p.inner_text,',',id,',',password,',',name,("\n"))
			end
		end
	end
end
def check_only(filename_id,filename_out)
	if(filename_id && filename_out) then
	else
		puts("Usage:intentioncheck.rb input/id.txt output/filename_out")
		return
	end
	id_file = open(filename_id)
	finish_file = open(filename_out,"w")
	while(id_text = id_file.gets) do
		id_array = id_text.split(/,/)
		puts id_array[2]
		id_array[2].chomp!
	id = id_array[0]
		password = id_array[1]
		name = id_array[2]
		begin
			timeout(30){
				check_unit(id,password,name,'tekitou','tekitou2',finish_file)
			}
			rescue Timeout::Error
			retry
		end
	end
	id_file.close
	finish_file.close
end
def check_and_regist(filename_id,filename_out)
	id_file = open(filename_id)
	finish_file = open(filename_out,"w")
	while(id_text = id_file.gets) do
		if(/----/=~id_text)then
			print(id_text.chomp," next day----\n")
			next
		end
		id_array = id_text.split(/,/)
		date = id_array[0]
		range_time = id_array[1]
		id = id_array[2]
		password = id_array[3]
		name = id_array[4].chomp
		print(date,range_time,name,"\n")
		check_unit(id,password,name,date,range_time,finish_file)
	end
	id_file.close
	finish_file.close
end
#if(ARGV[2])then
#	check_and_regist(ARGV[0],ARGV[1])
#end
#if(ARGV[1])then
#	check_only(ARGV[0],ARGV[1])
#else
#	puts("Usage:intentioncheck.rb input/id.txt output/filename_out")
#	puts("Usage:intentioncheck.rb input/int_file output/filename_out -resist")
#end