Posted by
okkez
Thu, 25 Dec 2008 23:28:00 GMT
今、一生懸命読んでます。コンピュータサイエンスをマジメに勉強した事の無い人や SICP に挫折した人にはちょうどいいかもしれません。演習問題の解答例が直後に載ってるのがいいです。さすがは教科書。
解答例のコーディングスタイルが微妙におかしいのは、丸写ししないようにかなぁ?
Posted in Ruby |
コメントはありません |
Posted by
okkez
Tue, 16 Dec 2008 17:17:00 GMT
解決したのでメモっとく。
環境変数 GEM_HOME を設定するなどして、標準のインストール先と違う場所に Gem パッケージをインストールしていると
emacs 上から rcodetools が動かない。
とりあえずシステムに rcodetools をインストールしてさっくり解決。
多分、emacs にわかるように Gem 関連の設定を環境変数経由でやればシステムに rcodetools をインストールしなくても
行けそうな気がするけど、試してない。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Sat, 13 Dec 2008 15:00:00 GMT
今日は SICP 読書会だったので復習がてら Ruby で書いてみた。
分かってしまえば簡単に書けた。
zero = lambda{|f|
lambda{|x| x }
}
one = lambda{|f|
lambda{|x|
f.call(x)
}
}
two = lambda{|f|
lambda{|x|
f.call(f.call(x))
}
}
three = lambda{|f|
lambda{|x|
f.call(f.call(f.call(x)))
}
}
p zero.call(lambda{|x| x + 1 }).call(0) # => 0
p one.call(lambda{|x| x + 1 }).call(0) # => 1
p two.call(lambda{|x| x + 1 }).call(0) # => 2
p three.call(lambda{|x| x + 1 }).call(0) # => 3
add = lambda{|a, b|
lambda{|f|
lambda{|x|
a.call(f).call(b.call(f).call(x))
}
}
}
p add.call(two, three).call(lambda{|x| x + 1 }).call(0) # => 5
Posted in Ruby |
コメントはありません |
Posted by
okkez
Sun, 12 Oct 2008 00:05:00 GMT
試しに見てみるサーバ。
#!/bin/sh
cd $HOME/ruby/rubydoc/refm/api
ruby $HOME/ruby/bitclust/standalone.rb \
–baseurl=http://localhost:10088 \
–port=10088 \
–database=./db \
–debug
るりまの DB をコンパイルする。
#!/bin/sh
VERSION=${1:-‘1.8.7’}
cd $HOME/ruby/rubydoc/refm/api
rm -rf ./db
bitclust -d ./db init version=$VERSION encoding=euc-jp
bitclust -d ./db update –stdlibtree=src
全てのバージョン。
#!/usr/bin/ruby
BITCLUST = ‘/home/kenji/bin/bitclust’
VERSIONS = %w[
1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7
1.9.0
]
REF_BASE=’/home/kenji/ruby/rubydoc/refm/api’
def setup_db(version)
db = “#{REF_BASE}/db#{version}”
`rm -rf #{db}`
`#{BITCLUST} -d #{db} init version=#{version} encoding=euc-jp`
`#{BITCLUST} -d #{db} update –stdlibtree=#{REF_BASE}/src`
puts “#{version} is done”
end
system “svn up -q #{REF_BASE}”
VERSIONS.reverse_each do |version|
setup_db(version)
end
こんな感じです。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Mon, 15 Sep 2008 08:45:00 GMT
両方試した結果、しばらく後者で運用することにしました。
理由は以下。
- サーバマシンのメモリが 256MB しかない
- thin はプロセスごとの消費メモリが 50MB から 100MB 程度
- Rails アプリを三つ動かしてるので thin のプロセスが多い (6プロセス)
そんな感じでメモリの消費が多くてスワップしまくりだったのが遅い原因でした。
Rails アプリで nginx + thin を使うときは thin のプロセス数 * 100MB を以上搭載するのが目安になりそうです。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Mon, 01 Sep 2008 09:30:00 GMT
やってみた。
色々と試行錯誤した結果なんとか安定したのでその手順をメモしておく。
インストール
nginx からソースを落としてビルドする。
$ ./configure –prefix=/usr/local
$ make
$ sudo make install
Thin - yet another web server は RubyGems でインストールする。
$ sudo gem install thin eventmachine
設定
thin の設定はコマンドラインでサンプルファイルを生成して、 /etc/thin 以下へコピーするだけ。
サーバの数を増やしすぎると、パフォーマンスが悪くなるので程々にしておく。
うちの場合は Rails アプリが三つあるのでそれぞれに二つずつサーバを起動するようにした。
# 三つずつにした場合は極端にパフォーマンスが悪化した。
$ thin config -C thin.yml -s2 -S /tmp/thin-qa.sock -u www-data -g www-data
$ vim thin.yml # 都合に合わせて編集する。
$ sudo cp thin.yml /etc/thin/
$ sudo thin install # /etc/init.d/thin を作成する
nginx の設定は Main - Nginx Wiki を参照して行う。
# 一部抜粋
# backend settings
upstream thin-typo {
server unix:/tmp/thin-typo.0.sock;
server unix:/tmp/thin-typo.1.sock;
}
# virtual host settings
# typo (blog)
server {
listen 80;
server_name typo.okkez.net;
access_log logs/typo.access.log main;
root /home/kenji/ruby/typo/public;
index index.html;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
# static files
if (-f $request_filename){
break;
}
# rails caching
if (-f $request_filename.html){
rewrite (.*) $1.html break;
}
# dynamic request
if (!-f $request_filename){
proxy_pass http://thin-typo;
break;
}
}
}
移行
- Apache の設定を変更 (Listen 8080 にする)
- thin を起動
- Apache を再起動
- nginx を起動
- 動作確認
以降の設定変更は気をつける。
- thin -C 設定ファイル restart
- kill -HUP nginx の pid (pid ファイルを参照)
** すぐには反映されないので注意する
- Apache は普通に再起動する
はまったポイント
- proxy の設定
- rewrite の設定
- 設定の再読み込みが出来ていなかったこと (kill -HUP pid)
疑問点
- 特定のアプリで prototype.js の読み込みはすぐに終わるのにその後の動きが鈍い
** 読み込まなければ速い
Posted in Ruby, Debian |
コメントはありません |
Posted by
okkez
Fri, 29 Aug 2008 07:03:00 GMT
JRuby で mongrel を動かしたくて mongrel-1.1.5-java を入れたけど動かなかった。
それをそのまま放置していたら普通の Ruby で mongrel が動かなくて焦った。
mongrel-java を消したら解決したけど、焦った。
たぶん、原因はロードパスがおかしくなったことだと思う。
ちょっとこれ以上はわからない。。。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Fri, 29 Aug 2008 02:31:00 GMT
とりあえず、動いたので手順だけ。
必要なもの。
- JRuby 1.1.3
- Tomcat6 (Tomcat5.5だと動かなかった)
- Gem (必要なものを入れる)
** activerecord-jdbc-adapter (0.8.2)
** activerecord-jdbcmysql-adapter (0.8.2)
** activerecord-jdbcpostgresql-adapter (0.8.2)
** activerecord-jdbcsqlite3-adapter (0.8.2)
** jdbc-mysql (5.0.4)
** jdbc-postgres (8.2)
** jdbc-sqlite3 (3.5.8)
** warbler (0.9.10)
- database.yml を必要に応じて編集する
$ cd $RAILS_ROOT
$ warble config
… config/warble.rb を編集する
Warbler::Config.new do |config|
config.dirs = %w(app config lib log vendor tmp)
config.includes = FileList[“po/**/*.po”, “locale/**/*.mo”]
config.gems << "activerecord-jdbcpostgresql-adapter"
config.gem_dependencies = true
config.webxml.rails.env = 'development'
end
...
$ warble
... カレントディレクトリに *.war が作成される
... $TOMCAT_ROOT/webapps 以下にコピーする
... tomcat を再起動する
... 以上
こんな感じでとりあえずは動いた。
けど mongrel の二倍位遅かった。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Wed, 27 Aug 2008 23:25:00 GMT
REXML DoS 脆弱性対策をしようとして typo があったため落ちてましたorz
落ちてる間に見ようとした人達がもしいたら、ご迷惑をお掛けしました。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Fri, 22 Aug 2008 03:48:00 GMT
Hash#default_proc= がなかったのであるハッシュにあとで default_proc をセットする方法を考えてみました。
以下のようにするとうまく行きます。
hash = {} # default_proc が無い。
hash.default_proc # => nil
other_hash = Hash.new{|h, k| h[k] = [] }.merge(hash)
other_hash.default_proc # => #
Posted in Programming, Ruby |
コメントはありません |
Posted by
okkez
Fri, 22 Aug 2008 03:04:00 GMT
続き。
とりあえず、ページを編集できるところまで確認。
作業手順をまとめると以下のような感じ。
- hiki.ru を用意する。
- hiki.rb を用意する。
- Hiki::App クラスを作って call メソッドを定義する
- @cgi を使っている部分を全て @request に変更する
- params[‘hoge’][0] を全て params[‘hoge’] に変更する
- ENV を使用している部分を @request.env に変更する
- クッキーを使用している部分を修正する
- print している部分を Rack::Response 的なものを返すように変更
- [ヘッダー,ステータス,ボディ] な配列で OK
- 面倒なのでヘッダーとボディだけ返してステータスは常に 200 にしといた
- Rack::Lint によるとヘッダーに Status を入れてはいけないらしいのでそれを入れてる部分を削除
- その他こまごまとした修正が必要。
まとめ
頑張ればできる。
いや、でも Hiki のソースも意外とひどいというのがわかった。統一感がないというかカオス一歩手前というか。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Thu, 21 Aug 2008 15:34:00 GMT
Hiki - Hiki -Front Page- を Rack: a Ruby Webserver Interface に載せる実験中。
概ねうまく行きそうな感じだけど、セッションまわりに苦戦中。あと、たぶんプラグインを全部修正しないといけないのが辛いかもしれない。
ちゃんと出来たら公開する予定。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Tue, 19 Aug 2008 05:31:00 GMT
行ってきました。
ちょっと睡眠不足気味だったので各セッションの記憶はあまりなかったりします。
なので今回は行ったという記録のみ残しておきます。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Wed, 18 Jun 2008 08:10:00 GMT
日本Rubyの会 公式Wiki - 第27回 Ruby/Rails勉強会@関西
もうすぐ開催されます。
今回はかずひこさんも久しぶりに登壇しますので、すごく楽しみです。
お時間のある方は、上記URIに申し込み方法が書いてあるので、そちらを参照の上、参加申し込みしてくださいね。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Thu, 12 Jun 2008 07:52:00 GMT
Posted in Ruby |
コメントはありません |
Posted by
okkez
Tue, 15 Apr 2008 13:58:00 GMT
Overview — Phusion Passenger (a.k.a. mod_rails)
思ったより簡単だったのでちょっとだけ手順を書いておく。
- Apache は 2.2 系
- Rails2.0.2
- Redmine0.7.0RC1
上記はインストールできている and 動作確認できている前提で。
まずは、Passenger をインストール。
sudo gem install passenger
で以下のコマンドを実行。
passenger-install-apache2-module
そうすると対話型っぽい感じになって色々と親切に教えてくれる。足りないライブラリとそれらの入手方法とか。
個人的にはもう少し不親切でもいいような気がした。
このコマンドではApacheのモジュールをビルドするので Windows ではかなり苦労するであろう事が予想される。(調べてないけど)
最後に、httpd.conf に書く設定内容が表示されるのでそれをコピペして Apache を再起動すればOK。
各アプリの設定は後で書く。
もう一つのインストール方法でやる場合は、ソースを展開したディレクトリの下にビルドしてインストールされてしまうので、ちょっと気持ち悪い。手でコピーすればいいけどコマンドが動的に生成されるっぽいのでその辺の調整が必要。
### Redmine を使う
- /var/webapps/redmine に置いている
- /var/www/html が Apache の DocumentRoot
サブドメインが使える場合は以下でOK。
ServerName redmine.example.com
DocumentRoot /var/webapps/redmine/public
サブドメインが使えない場合(素朴なイントラネットなど)。
ln -s /var/webapps/redmine/public /var/www/html/redmine
のようにシンボリックリンクを DocumentRoot 以下に用意する。
で、以下の内容を httpd.conf を追加する。
DocumentRoot /var/www/html
RailsBaseURI /redmine
サブドメインが使える場合は NameBase の VirtualHost でいくらでもアプリケーションを置くことができるが、そうでない場合は以下のようにすればいけるかもしれない。(まだ試してない)
RailsBaseURI /redmine1
RailsBaseURI /redmine2
考えてみたけど VirtualHost を使用せずに一つのアプリに別々のRailsEnv を設置する方法がわからない。
上のやり方だと、一つの環境しか設定できないような気がするし。
以上の設定内容は全部、 Passenger のサイトに書いてあった。最初からドキュメントが揃っているのは素晴らしい!
Posted in Ruby |
コメントはありません |
Posted by
okkez
Fri, 22 Feb 2008 15:04:00 GMT
ざっとこんなもんかな。時間ある時に勝手に添削する。
# 添削希望のものだけね。
Kanasan の 100 マス計算
趣味も大分入るけど以下のような感じで添削しました。
- 題意を勘違いしてる?
- 演算子もランダムにして欲しかったけどこれはこれでアリなので活かすことにする
- 使える演算子は決まっているので、定数化する
- 入力値のチェックに正規表現を使っていたがこの程度なら文字列比較で十分
- 一度初期化したら変わらない変数は定数にする
- メソッド定義のカッコは省略しない
- exit 1 にしとく
- コマンドの失敗時の戻り値は 1 であることが多いため
- 一文字変数はスコープが短い所で使用する
- 配列の変数名は配列であることが予想できるような名前にする
kanasan's 100マス計算添削後
#! /usr/bin/ruby
OPERATORS = %w[+ - * / %]
DIGIT = 2
SEPARATOR = " "
VERTICAL_LINE = "|"
HORIZONTAL_LINE = "-"
CROSS_LINE = "+"
operator = ARGV[0].to_s
unless OPERATORS.include?(operator)
puts "wrong parameter: operator is only +-*/%"
exit 1
end
def display100Math(xs, ys, operator, display_answer)
print SEPARATOR * (DIGIT - operator.size)
print operator
print SEPARATOR
print VERTICAL_LINE
xs.to_a.each do |x|
print SEPARATOR
print SEPARATOR * (DIGIT - x.to_s.size)
print x
end
puts ""
print HORIZONTAL_LINE * DIGIT
print HORIZONTAL_LINE
print CROSS_LINE
xs.size.times do
print HORIZONTAL_LINE
print HORIZONTAL_LINE * DIGIT
end
puts ""
ys.each do |y|
print SEPARATOR * (DIGIT - y.to_s.size)
print y
print SEPARATOR
print VERTICAL_LINE
if display_answer
xs.each do |x|
answer = y.send(operator, x)
print SEPARATOR
print SEPARATOR * (DIGIT - answer.to_s.size)
print answer
end
end
puts ""
end
end
xs = (1..9).sort_by{rand}
ys = (1..9).sort_by{rand}
display100Math(xs, ys, operator, false)
puts ""
display100Math(xs, ys, operator, true)
やらなかったこと
sixeight さんの 100 マス計算
同じく趣味が入るけどこんな感じ。
- いくつかの変数名/メソッド名を変更した
- initialize に仕事積めすぎなので @header を make_question に移動
- そうするとインスタンス変数である必要がなくなるのでローカル変数にした
- make_matrix は表作成専用にした
適切に仕事を分割することでよりよくなったはず。
sixeight's 100 マス計算添削後
class Hyakumasu
def initialize
@op = [:+, :-, :*, :/][rand(4)]
@row = (0..9).sort_by{ rand }
@col = (0..9).sort_by{ rand }
end
def make_matrix
header = "#{@op} | " + @col.join(' ') + "\n--|" + "---" * @col.length
@row.inject(header){|r, v|
r + "\n#{v} |" + (block_given? ? yield(v) : '')
}
end
def display
puts make_matrix
puts
puts make_matrix{|v|
@row.map {|i|
"%3d" %((@op == :/ && i.zero?) ? 0 : v.send(@op, i))
}.join
}
end
end
Hyakumasu.new.display
Posted in Programming, Ruby |
コメントはありません |
Posted by
okkez
Sun, 17 Feb 2008 13:56:00 GMT
いつも通りみんなの集合時間より早く行って簡単な打ち合わせ。
日本Rubyの会 公式Wiki - 第23回 Ruby/Rails勉強会@関西
Ruby1.9 の仕様 − Array 関連の新しい機能 by こなみ さん
よくも悪くも講義?って感じだった。
最後の方の内容が面白かっただけに尻切れとんぼっぽくなったのが残念。
純粋関数型言語Ruby(3) 30分でわかるMonad by 氏久(ujihisa)さん
やっぱり 30min ではわからなかった Monad
参加者大半おいてけぼり??
Rails以外のWebアプリケーションフレームワーク」 by yhara さん
merb, capmping, ramaze の紹介。前二つはストヤン経由で聞いてたけど結局試してなくてごめんなさい。
今度、試す。
Rubyのリフレクション by 大林さん
おもしろかった。久しぶりに燃えるセッションだった。
# って自分こんなにリフレクション好きだったっけ?
すごく勉強になった。要復習。
これくらいの濃さのセッションが毎回あるといいのだけど。
Ruby初級者向けレッスン第17回 by okkezさん
自分のセッション。
初級者レッスンと言いつつ、るりまの宣伝乙。
演習問題もリファレンスを見てやってもらう前提だったのでこんなものかと。
演習問題の最後に、使うかもしれないメソッド一覧を載せたのは良かったらしい。Keep
そう言えば、自分が発表する場合の時間の読み方がかなり正確になってきた気がする。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Wed, 23 Jan 2008 13:18:00 GMT
Ruby には暦週を求める Date#cweek というメソッドがあるが、こいつは月曜日スタートで日曜日で終わる。
でも、たまに日曜日から土曜日での暦週が欲しくなることがある。というか今日必要になった。
なので作ってみた。
class Date
def myweek
if wday == 0
(self + 3).cweek
# cweek + 1 でいいかも。。。
else
cweek
end
end
end
とりあえず試した範囲では上手く動いてた。
Posted in Ruby |
コメントはありません |
Posted by
okkez
Sun, 04 Nov 2007 03:22:00 GMT
Test::Unit版
と同じことを RSpec でやってみた。
第 20 回 Ruby 勉強会@関西
に解答例(answer.pdf)がアップされていますがそれの捕捉説明的なものです。前回と同様、配布資料(print.pdf)が手元にある前提で進めます。
Test::Unit 版とは書いた時期が違うので微妙に実装が違っていたりします。
ちなみに RSpec を使って書くのは初めてです。RSpec についてはるびまの記事を参照してください。
なお、stack_spec.rb は、そのとき注目している機能に関係のある部分のみ掲載します。
実装するメソッド
- empty?
- スタックが空なら true、そうでなければ false を返す。
- size
- push(val)
- pop
- スタックの一番上の値を取り除いて返す。スタックが空の場合は Stack::EmptyStackErrorが発生する。
Step1
- Stack#empty? のテスト
- 「新しいスタックの empty? は真」
stack_spec.rb
require 'stack'
describe Stack, "when empty" do
before do
@empty_stack = Stack.new
end
it "should be empty" do
@empty_stack.should be_empty
end
after do
@empty_stack = nil
end
end
Stack が空のときの動作を記述。
- spec -c で実行すると色つきで結果が表示される
stack.rb
class Stack
def empty?
true
end
end
- とりあえず Test::Unit 版と同じように Fake it してみた
Step2
- Stack#push と Stack#pop のテスト
- 「新しいスタックに 3 を push して pop すると 3 が返る」
stack_spec.rb
describe Stack, "when push 3" do
before do
@stack = Stack.new
@stack.push(3)
end
it "should pop 3" do
@stack.pop.should == 3
end
after do
@stack = nil
end
end
- describe の引数に書いた内容を before のブロックに記述する
- it 〜 end に実際にチェックしたい内容を記述する
- after のブロックに後始末の処理を記述する
- before と after は省略可能
stack.rb
class Stack
def empty?
true
end
def push(val)
end
def pop
3
end
end
- Test::Unit 版と同じように Fake it
Step3
- Stack#size のテスト
- 「新しいスタックに 3 を push すると size は 1」
stack_spec.rb
describe Stack, "when push 3" do
before do
@stack = Stack.new
@stack.push(3)
end
it "should pop 3" do
@stack.pop.should == 3
end
it "should size 1" do
@stack.size.should == 1
end
after do
@stack = nil
end
end
- Step2 と事前準備の内容は同じなので同じ describe ブロック内に it 〜 end を追加する
stack.rb
class Stack
def empty?
true
end
def push(val)
end
def pop
3
end
def size
1
end
end
Step4
- Stack#size のテスト (2)
- 「新しいスタックに 3 を push すると size は 1」
- 「さらに 5 を push すると size は 2」
stack_spec.rb
describe Stack, "when push 3 and 5" do
before do
@stack = Stack.new
@stack.push(3)
@stack.push(5)
end
it "should size 2" do
@stack.size.should == 2
end
end
- 前のステップとは状況が違うので新しい describe ブロックを用意した
stack.rb
class Stack
def initialize
@size = 0
end
def empty?
true
end
def push(val)
@size += 1
end
def pop
3
end
def size
@size
end
end
- Fake it のままでは通らないので、ちょっとまじめに実装。
Step5
- Stack#empty? のテスト (2)
- 「新しいスタックに 3 を push すると empty? は偽」
stack_spec.rb
describe Stack, "when push 3" do
before do
@stack = Stack.new
@stack.push(3)
end
it "should pop 3" do
@stack.pop.should == 3
end
it "should size 1" do
@stack.size.should == 1
end
it "should not be empty" do
@stack.should_not be_empty
end
after do
@stack = nil
end
end
- 事前準備の状況が Step2, Step3 と同じなので同じ describe に振舞を追加
stack.rb
class Stack
def initialize
@size = 0
end
def empty?
size == 0
end
def push(val)
@size += 1
end
def pop
3
end
def size
@size
end
end
Step6
- Stack#pop のテスト (2)
- 「新しいスタックを pop すると Stack::EmptyStackError が発生する」
stack_spec.rb
describe Stack, "when empty" do
before do
@empty_stack = Stack.new
end
it "should be empty" do
@empty_stack.should be_empty
end
it "should raise Stack::EmptyStackError" do
lambda{
@empty_stack.pop
}.should raise_error Stack::EmptyStackError
end
after do
@empty_stack = nil
end
end
stack.rb
class Stack
class EmptyStackError < StandardError
end
def initialize
@size = 0
end
def empty?
size == 0
end
def push(val)
@size += 1
end
def pop
raise EmptyStackError if empty?
3
end
def size
@size
end
end
Step7
- Stack#pop のテスト (3)
- 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」
stack_spec.rb
describe Stack, "when push 3 and 5, then pop" do
before do
@stack = Stack.new
@stack.push(3)
@stack.push(5)
@stack.pop
end
it "should size 1" do
@stack.size.should == 1
end
after do
@stack = nil
end
end
stack.rb
class Stack
class EmptyStackError < StandardError
end
def initialize
@size = 0
end
def empty?
size == 0
end
def push(val)
@size += 1
end
def pop
raise EmptyStackError if empty?
@size -= 1
3
end
def size
@size
end
end
Step8
- Stack#pop のテスト (4)
- 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」
stack_spec.rb
describe Stack, "when push 3 and 5" do
before do
@stack = Stack.new
@stack.push(3)
@stack.push(5)
end
it "should size 2" do
@stack.size.should == 2
end
it "should pop 5" do
@stack.pop.should == 5
end
after do
@stack = nil
end
end
stack.rb
class Stack
class EmptyStackError < StandardError
end
def initialize
@size = 0
@stack = []
end
def empty?
size == 0
end
def push(val)
@size += 1
@stack.push(val)
end
def pop
raise EmptyStackError if empty?
@size -= 1
@stack.pop
end
def size
@size
end
end
Step9
最後にリファクタリングします。
stack_spec.rb
require 'stack'
describe Stack, "when empty" do
before do
@empty_stack = Stack.new
end
it "should be empty" do
@empty_stack.should be_empty
end
it "should raise Stack::EmptyStackError" do
lambda{
@empty_stack.pop
}.should raise_error Stack::EmptyStackError
end
after do
@empty_stack = nil
end
end
describe Stack, "when push 3" do
before do
@stack = Stack.new
@stack.push(3)
end
it "should pop 3" do
@stack.pop.should == 3
end
it "should size 1" do
@stack.size.should == 1
end
it "should not be enpty" do
@stack.should_not be_empty
end
after do
@stack = nil
end
end
describe Stack, "when push 3 and 5" do
before do
@stack = Stack.new
@stack.push(3)
@stack.push(5)
end
it "should size 2" do
@stack.size.should == 2
end
it "should pop 5" do
@stack.pop.should == 5
end
after do
@stack = nil
end
end
describe Stack, "when push 3 and 5, then pop" do
before do
@stack = Stack.new
@stack.push(3)
@stack.push(5)
@stack.pop
end
it "should size 1" do
@stack.size.should == 1
end
after do
@stack = nil
end
end
- 上記の仕様を満たすように実装コードを書き換えます。
- 本来はちょっとずつ書き換えるべきですが、最終結果のみ書いておきます
stack.rb
require 'forwardable'
class Stack
extend Forwardable
def_delegators(:@stack, :empty?, :push, :size)
def initialize
@stack = []
end
def pop
raise EmptyStackError if empty?
@stack.pop
end
class EmptyStackError < StandardError
end
end
- forwardable ライブラリを使用するとメソッドを指定して特定のオブジェクトに委譲できる
- 使用方法はリファレンスマニュアルを確認してください
Posted in Ruby |
コメントはありません |
Posted by
okkez
Fri, 02 Nov 2007 11:52:00 GMT
行ってきた。
第 20 回 Ruby 勉強会
内容は置いといて、初級者レッスンの解答例(answer.pdf)に補足説明を入れておく。
資料は上記サイトでダウンロードしてくださいな。
以下では、配布資料(print.pdf)が手元にあることを前提に進めていきます。
実装するメソッド
- empty?
- スタックが空なら true、そうでなければ false を返す。
- size
- push(val)
- pop
- スタックの一番上の値を取り除いて返す。
スタックが空の場合は Stack::EmptyStackErrorが発生する。
テスト駆動開発版
TDD でやってくのです。
Step1
- Stack#empty? のテスト
- 「新しいスタックの empty? は真」
ということなのでまずはテストを書きます。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
end
空っぽのテストなので簡単ですね。
* Stack#empty? のテストをしたいのでテストメソッド名は test\_empty?
* assert は 第一引数の式が真で無い場合にに失敗する
で、実装。普通は失敗してから Fake it するのですが、print.pdfでは既に失敗してるのでここではいきなり成功しときます。
stack.rb
class Stack
def empty?
true
end
end
ここでは成功さえすれば良いのでいきなり true を返します。
#### Step2
* Stack#push と Stack#pop のテスト
* 「新しいスタックに 3 を push して pop すると 3 が返る」
まずテスト。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
end
* Stack#push と Stack#pop のテストなのでテストメソッド名は test\_push\_and\_pop
stack.rb
class Stack
def empty?
true
end
def push(val)
end
def pop
return 3
end
end
* Stack#push はテストが通るので、まだ空っぽでいい。
* Stack#pop はテストを通すために 3 を返す。
明らかにテストが不足しているが、まだ書かない。一歩ずつ確実に進んで行く。
#### Step3
* Stack#size のテスト
* 「新しいスタックに 3 を push すると size は 1」
なにがなんでもまずテスト。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
end
end
* push してから size を確認するのでテストメソッド名は test\_push\_and\_size
* まだ一個しか push しない
* 二個 push したらどうなる?とかこの段階では考えない。
stack.rb
class Stack
def empty?
true
end
def push(val)
end
def pop
return 3
end
def size
1
end
end
* 3 を push したら size が 1 になればいいのだから Stack#size は 1 を返せばいい
* しつこく Fake it
* テストを通すことだけ考える。
#### Step4
* Stack#size のテスト (2)
* 「新しいスタックに 3 を push すると size は 1」
* 「さらに 5 を push すると size は 2」
とりあえずテストを書いてみる。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
@stack.push(5)
assert_equal(2, @stack.size, 'push increments the size.')
end
end
素直に書けばこうなるはず。
実装コード
stack.rb
class Stack
def initialize
@size = 0
end
def empty?
true
end
def push(val)
@size += 1
end
def pop
return 3
end
def size
return @size
end
end
* Fake it では通らなくなったので、マジメに実装。
* 放っておいても今のところ大丈夫な部分は放置。
#### Step5
* Stack#empty? のテスト (2)
* 「新しいスタックに 3 を push すると empty? は偽」
いつまでも Stack#empty? が true しか返さないのはまずいので、false を返す場合のテストを書いてみる。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
@stack.push(5)
assert_equal(2, @stack.size, 'push increments the size.')
end
def test_push_and_empty?
@stack.push(3)
assert_equal(false, @stack.empty?, 'a stack with data is not empty.')
end
end
で、テストが失敗するのでテストが通るように実装コードを変更します。
stack.rb
class Stack
def initialize
@size = 0
end
def empty?
return @size == 0
end
def push(val)
@size += 1
end
def pop
return 3
end
def size
return @size
end
end
* @size == 0 が true/false のいずれかを返すので return します
* 関係ない部分は弄りません
#### Step6
* Stack#pop のテスト (2)
* 「新しいスタックを pop すると Stack::EmptyStackError が発生する」
誰がなんと言おうがテストコードを先に書きます。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
@stack.push(5)
assert_equal(2, @stack.size, 'push increments the size.')
end
def test_push_and_empty?
@stack.push(3)
assert_equal(false, @stack.empty?, 'a stack with data is not empty.')
end
def test_empty_pop
assert_raise(Stack::EmptyStackError,
'to pop a empty stack raise an error.') {@stack.pop}
end
end
* 空のスタックに対して pop するのでテストメソッドは test\_empty\_pop
* assert_raise はブロックに例外が発生する可能性のある処理を記述する
実装コード
stack.rb
class Stack
class EmptyStackError < StandardError; end
def initialize
@size = 0
end
def empty?
return @size == 0
end
def push(val)
@size += 1
end
def pop
raise EmptyStackError if empty?
return 3
end
def size
return @size
end
end
* Stack#pop を修正。
* Stack#empty? が true の場合に例外を発生させる
* 関係ない部分は意地でも書き換えない
#### Step7
* Stack#pop のテスト (3)
* 「新しいスタックに 3 を push して 5 を push して pop すると size は 1」
例によってテストを先に書きましょう。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
@stack.push(5)
assert_equal(2, @stack.size, 'push increments the size.')
end
def test_push_and_empty?
@stack.push(3)
assert_equal(false, @stack.empty?, 'a stack with data is not empty.')
end
def test_empty_pop
assert_raise(Stack::EmptyStackError,
'to pop a empty stack raise an error.') {@stack.pop}
end
def test_push_push_pop_and_size
@stack.push(3)
@stack.push(5)
@stack.pop
assert_equal(1, @stack.size, 'pop decrements the size.')
end
end
* テストメソッド名は test\_push\_push\_pop\_and\_size
* 特別なことはしていません
実装コード
stack.rb
class Stack
class EmptyStackError < StandardError; end
def initialize
@size = 0
end
def empty?
return @size == 0
end
def push(val)
@size += 1
end
def pop
raise EmptyStackError if empty?
@size -= 1
return 3
end
def size
return @size
end
end
* 関係ない部分は弄らない、と
#### Step8
* Stack#pop のテスト (4)
* 「新しいスタックに 3 を push して 5 を pushして pop すると 5 が返る」
そろそろ佳境ですが、テストを先に書きます。
test_stack.rb
require 'stack'
require 'test/unit'
class TestStack < Test::Unit::TestCase
def setup
@stack = Stack.new
end
def test_empty?
assert(@stack.empty?, 'a new stack is empty.')
end
def test_push_and_pop
@stack.push(3)
assert_equal(3, @stack.pop, 'pop returns the last value.')
end
def test_push_and_size
@stack.push(3)
assert_equal(1, @stack.size, 'push increments the size.')
@stack.push(5)
assert_equal(2, @stack.size, 'push increments the size.')
end
def test_push_and_empty?
@stack.push(3)
assert_equal(false, @stack.empty?, 'a stack with data is not empty.')
end
def test_empty_pop
assert_raise(Stack::EmptyStackError,
'to pop a empty stack raise an error.') {@stack.pop}
end
def test_push_push_pop_and_size
@stack.push(3)
@stack.push(5)
@stack.pop
assert_equal(1, @stack.size, 'pop decrements the size.')
end
def test_push_push_and_pop
@stack.push(3)
@stack.push(5)
assert_equal(5, @stack.pop, 'pop returns the last value.')
end
end
実装コード
stack.rb
class Stack
class EmptyStackError < StandardError; end
def initialize
@size = 0
@values = Array.new
end
def empty?
return @size == 0
end
def push(val)
@size += 1
@values[@size - 1] = val
end
def pop
raise EmptyStackError if empty?
val = @values[@size - 1]
@size -= 1
return val
end
def size
return @size
end
end
外から見た動作ではスタックぽく動くようになりましたね。
さて、 irb で中身を覗いてみましょう。
irb(main):001:0> load 'stack.rb'
=> true
irb(main):002:0> s = Stack.new
=> #
irb(main):003:0> s.push 1
=> 1
irb(main):004:0> s.push 2
=> 2
irb(main):005:0> s.push 3
=> 3
irb(main):006:0> s.inspect
=> "#"
irb(main):007:0> s.pop
=> 3
irb(main):008:0> s.pop
=> 2
irb(main):009:0> s.pop
=> 1
irb(main):010:0> s.inspect
=> "#"
irb(main):011:0>
@values の中身が Stack#pop しても消えませんね。これはスタックとしては拙いのでリファクタリングしましょう。
というわけで次回に続く。
Posted in Ruby |
コメントはありません |