ブロックを任意のメソッドに付けられる?
- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (118件) を見る
プログラミング言語 Ruby をつらつらと読んでいて
ふと引っかかったのでメモ.
6.4.5.1 メソッド呼び出しにおける &
P. 198
(前略)... ブロックは,メソッドがブロックを予定していなくても,yieldを使わなくても,任意のメソッド呼び出しに付けられる.
# ruby 1.9.2 で試す # 適当なメソッド def hello(x) puts "Hello, " + x.to_s end # inject に使いそうなProcオブジェクト proc = Proc.new {| total , x | total + x} hello("World", &proc) #=> Hello, World # 適当なブロックを渡してみる hello("World") {|x| p } #=> Hello, World
たしかにブロックを受け取る予定のないメソッドにProcオブジェクトを渡しても引数の例外(ArgumentError)が出ない.
出来るけど何が嬉しいのやら?
ちなみに,ブロックを受け付けるメソッドに不適切なProcオブジェクトを与えると当然のようにエラーになる.
%w(and but car).map &proc # 前述のProcオブジェクト TypeError: can't convert nil into String ....
rvm のgemsetをおさらい(Mac でRuby)
rvm ではrubyのバージョンだけではなく,gemの組合せも管理できるのです.
sinatraで開発するセット,rails 3を使うセット,...etc
開発内容にあわせて切り分けられるってわけですね.
前回のエントリ に引き続き
Snow Leopard でrubyの環境を整えるついでに
rvmのgemset についておさらいしておこうと思います.
やってみる
ruby 1.9.2 を例に使ってみます.
$ rvm use 1.9.2
最近お気に入りのnanoc用のセットを作ってみます.
$ rvm gemset create nanoc
できたセットを見てみると....
$ rvm gemset list global nanoc
globalというのが最初からあります.
このセットは共通して使うgemをインストールするために使うようです.
ではとりあえずglobalを利用してみます.
$ rvm gemset use global
これは
$ rvm use 1.9.2@global
もしくは
$ rvm use @global
も同じ意味です.
あらためて一覧を見てみると
$ rvm gemset list
=> global
nanoc
と現在使っているセットに"=>" と矢印がつきます.
いつでも使いそうな rdoc, ri , wirble をインストールして,
nanoc セットに切り替えてみると
$ rvm gemset use nanoc $ gem list *** LOCAL GEMS *** rake (0.9.2, 0.8.7) rdoc (3.6.1) wirble (0.1.3)
と,たしかにglobal でインストールしたgemが一覧に出てきます.
あとはnanoc での開発用に必要なgemをインストールして環境構築終了っ!
賢い使い方
新規でセットを作って即その環境を使うときは
$ rvm use --create 1.9.2@rails3
なんて一行で済みます.便利!
また,開発内容を切り替えるときに手動は面倒!なんてときは
開発内容のディレクトリに.rvmrc ファイルを置いて
rvm use 1.9.2@nanoc
なんて記述しておくと,ディレクトリを移動したときに
gemsetも切り替わっちゃいます.
ただし...
ディレクトリ移動のたびに切り替え確認を問われるので,
それが面倒な場合,homeの.rvmrcに設定を追加しておくとよいでしょう.
$ vi ~/.rvmrc
rvm_install_on_use_flag=1
使いそうなコマンド
ヘルプを見てみると...
$ rvm gemset help
Usage:
rvm gemset [action]
Action:
{import,export,create,copy,rename,empty,delete,name,dir,list,list_all,gemdir,install,pristine,clear,use,update,unpack,globalcache}
とずいぶんできることがあって知らないものも結構ある!
とりあえず使いそうなものだけピックアップしておくと...
$ rvm gemset create 名称 # セットを新規作成 $ rvm use 名称 # セットの切り替え $ rvm gemset empty # インストールしたgem を取り除く $ rvm gemset delete 名称 # セット自体を消去 $ rvm gemset install 名称 # gem install... と同じ
くらいでしょうか.
セットを作りすぎて迷子になったときは
$ rvm gemset name # 現在使っているセット $ rvm gemset list # 現在使っているruby のバージョン内のgemセット一覧 $ rvm gemset list_all # rubyのバージョンをまたいだgemセット一覧 $ rvm gemset dir # 現在使っているセットのディレクトリパス
が使えると便利!
追記
rubyのバージョンを変えて同じgemsetを使いたい場合
$ rvm gemset copy ruby1.9.2-p180@global ruby1.9.2-p290@global
のように簡単にコピーできます :-)
Snow Leopard でrvm 環境を整えなおした
外付けHDDの初期化やらBoot Campの作り直しをしていたら
Snow Leopardの領域まで吹き飛ばしてしまったのは4日前 :-(
以前書きためてた Macでのrvm(Ruby)環境整備のエントリ内容が古くなってきたので
改めて更新しておこうと思います.
前準備
rvmのパスを設定するために.bashrcが必要なのですが,
OSインストール後はターミナルの設定がまるっきりないので
.bash_profileと.bashrcを用意します.
ちなみに .bash_profile は.bashrcを呼ぶだけにしています.
$ echo source \~/.bashrc > ~/.bash_profile $ touch ~/.bashrc
rvm インストール
ターミナル上で下記のコマンドをペーストするだけ.
最新版のrvm が手に入ります.
$ bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
.bashrcがあればパス設定も書き込まれるのですが,
万一書き込まれない場合は
rvmインストール後のメッセージにある以下のような設定を.bashrcに追記します.
[[ -s "/Users/ユーザ名/.rvm/scripts/rvm" ]] && source "/Users/ユーザ名/.rvm/scripts/rvm"
無事パスが入ったら.bashrcを読み直し
$ source ~/.bashrc
readline インストール
irbを使うときに readline がないととっても不便なので先に入れておきます.
# readline無しだとirbを使うたびに警告が出るみたいです.
$ rvm package install readline
合わせて,各バージョンのrubyをインストールする際の
オプション設定にreadlineをセットしておきます.
$ vi ~/.rvm/user/db
# ↓追加する内容
ruby_configure_flags=--with-readline-dir=/Users/ユーザ名/.rvm/usr
ruby の各バージョンをインストール
自分の環境では以下のバージョンを入れました :-)
$ rvm install 1.9.2 # rails3 や普段使い用 $ rvm install ree # redmine のために入れておく (with rails 2) $ rvm install jruby # android連携とか. $ rvm install macruby # mac用のちょっとしたアプリ開発に
おまけ
rvm自体のアップグレード
インストールしたいrubyのバージョンがないときは
rvmのバージョンが古いことが考えられます.
そんな時は以下のようにrvm自体をアップグレードします.
$ rvm get head $ rvm reload
その他よく使うコマンド類
$ rvm list # インストール済み一覧を確認 $ rvm list known # インストールできる一覧を確認 $ rvm use バージョン # バージョン切り替え $ rvm gemset create セット名 # 特定のgemの組合せを用意する場合に使用 $ rvm use バージョン@ジェムセット名 # gemセットを指定して利用する場合
DotCloud ベータの初期設定をしてみた
Webアプリケーションホスティングサービスの DotCloud ベータ版の招待が届いたので
初期設定だけしてみました.
Ruby使いにはHeroku があれば良いのだけれども,
先日あったAWSの障害によってダメージがあったこともあり*1
複数のサービスを触っておいたほうが良いかなと思ってます.
「多様性は善」って言うし*2.
# DotCloudもAmazon EC2上にあるらしいのだけど*3
ベータ版申込み
メールアドレスを登録するだけ.
自分の場合,5月8日に申し込んで13日には招待が届いたので,
一週間も待てば招待が届くと思います.
Mac (Snow Leopard)
OS同梱のpython (2.6.1) を使えばOKです.
easy_install もすでに入っています.
Ubuntu 11.04
初期状態で入っているPython (2.7.1+) を使えばよいので,
easy_installを使えるようにpython-setuptoolsをインストールします.
$ sudo apt-get install python-setuptools
設定
ターミナルからeasy_install でインストール後,APIキーをセットしておきます.
APIキーは https://www.dotcloud.com/account/settings で確認できます.
$ sudo easy_install dotcloud $ dotcloud Warning: ~/.dotcloud/dotcloud.conf does not exist. Enter your api key (You can find it at http://www.dotcloud.com/account/settings): APIキーを貼り付ける
あとは任意のアプリケーション名を設定して
好みのタイプ*4のコンポーネントを配置して,
用意していたコードを送り込むだけ.
$ dotcloud create ramen $ dotcloud deploy --type python ramen.www $ dotcloud push ramen.www ~/work/ramen/www
http://www.ramen.dotcloud.com/
がサービスのURLです*5.
チュートリアルの例だと"ramen"という名前で用意しています.
このアプリケーション名がdotcloud内で重複しないようにとのこと.
あとは運用に載せたいサービスを用意するだけです.
自分は手始めにredmineをセットしてみようと思っています*6.
あまりに簡単で拍子抜けしました :-)
DotCloud のチュートリアルでは読みにくい方は以下が参考になります.
nanoc でSQLite3のデータからページを作る
同じようなコンテンツが大量にあるけれど,
静的なHTMLのみで作らなければならないサイトがあったので
nanoc*1 というRuby製のコンテンツビルドツールを使ってみました.
nanocってなにさ?
コンテンツのパーツを作っていき,一気に「コンパイル」してサイトを構成してくれる逸品です.
作ったパーツから同じ「タグ」要素の一覧ページを生成したり,
投稿日ごとのアーカイブを生成するなど,
ブログっぽいサイトを静的ページのみで作れます :-)
ブログサイトを作る過程は以下の記事がとても参考になりました.
前フリ
一日1ページのコンテンツを用意するにも,記事のファイルを一つ一つ書いて....
というのがとても面倒大変で,
しかもHTML書けない人(エンドユーザ)にデータを用意してもらうことも難しいので,
Rails で入力フォームをざっくり作り*2,
SQLite3のデータベースへコンテンツの要素を登録してもらいました.
で,ここからがnanocの話.
「静的なWebサイト」の制約があるのでRailsで公開コンテンツを作れないので
登録してもらったデータを読み込んでnanocでページを生成してみたわけです.
nanocのData Source
Nanoc3::DataSource というクラスを継承して
取得したデータからnanoc用のオブジェクト*3を作ることで,
「コンパイル」の際にデータを埋め込んだページを生成することができます.
Rails のフォームではSQLiteを使っていたので,ActiveRecord*4を使って実装してみました.
ここでは,日報を表す「report」テーブルから1レコード1ページで作る例を示します.
# ./lib 以下に用意 class ReportSource < Nanoc3::DataSource require 'sqlite3' require 'active_record' require 'yaml' # Data Sourceにつける名前.設定ファイルで利用 identifier :reports # nanoc用オブジェクトを配列で返すためのメソッド(オーバーライド) def items items_list = [] # Railsでの接続設定用ファイルを流用 # 作るサイトのディレクトリ直下にYAMLファイルを配置 ActiveRecord::Base.configurations = YAML.load_file("database.yml") # ActiveRecordでDBに接続 ActiveRecord::Base.establish_connection('development') # 全件取得 Reports.find(:all).each do |report| # HTMLファイルを作るためのテンプレート content = "<%= render('_report_page') %>" # ページに埋め込む属性(データ)のHash # {:フィールド名 => 値} というHashが.attributesメソッドで取得できる attributes = report.attributes # metaのtitle要素用に加工 attributes[:title] = "日報: " + report.titile # URLを決めるオブジェクト名 identifier = "/#{report.id}/" # ページの更新日付(デフォルトはnil) mtime = report.updated_at # nanocのコンテンツ用オブジェクトを用意 items_list << Nanoc3::Item.new(content, attributes, identifier, mtime) end items_list end end # ActiveRecord用にデータモデルの空クラスを用意 class Reports < ActiveRecord::Base end
各ページのテンプレート(上記の_report_page)は以下のように,erb形式でデータを埋め込んでいます.
<h2><%= item[:title] %><h2> <p> <b>Reporter:</b> <%= item[:reporter] %> </p> <p> <b>Report at:</b> <%= item[:report_at] %> </p> ....
Nanoc3::Item.new(content, attributes, identifier, mtime)
でセットしたattributesにはDBのレコードの各フィールドが埋めこまれていて,
「item」に格納されているので,上のようにitem[:フィールド名] で値を取り出しています.
「コンパイル」の時にこのDetaSourceを呼び出すために,
config.yamlの以下の部分に追記します.
.... data_sources: - # デフォルトの設定... - # 上記のReportSourceにある"identifier" type: reports # URLのルート.http://hogehoge/reports/id番号/ というページ構成にする items_root: /reports/ # レイアウトの配置 layouts_root: /
ここまでの設定で、コンパイル後に ./output/ ディレクトリに
./output/reports/1/index.html ./output/reports/2/index.html ./output/reports/3/index.html ./output/reports/4/index.html ....
とレコードの数だけコンテンツが生成されるわけです.(しかもRestぽい)
なぜActiveRecord?
SQLite::Databaseを使ってDBに接続しても良かったのですが,
取得したレコードをテンプレートに埋め込むHashを作る際に
以下のようにレコードの配列の番号を指定して...が面倒で短調でバグになりそうな臭いがするので
ActiveRecordでサッとHashを用意しました.
... db = SQLite3::Database.new("hogehoge.db") db.execute("select * from report") do |row| ... attributes = { :reporter => row[1], .... } end
ActiveRecordの使い方は以下を参考にしました.
集合知プログラミング 2章 - 類似度計算の比較
id:plasticscafe が「集合知プログラミング」をPythonからScalaに翻訳しながら写経していくというので,
尻馬に乗って自分もやった気になってみよう ちょっとお手伝いがてら自分もやってみました.
- 作者: Toby Segaran,當山仁健,鴨澤眞夫
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/07/25
- メディア: 大型本
- 購入: 91人 クリック: 2,220回
- この商品を含むブログ (267件) を見る
本題
「集合知プログラミング」2章のエクササイズに
- Tanimoto係数について調査、検討してみる
というのがあるそうなので,コサイン類似度と比較してみました*1.
以下,勘に頼る部分が大きいので詳しい人からコメントほしいっ!
数式で比較
比較するデータの組を下のようにn個ずつのベクトル(配列)として考えていきます.
,
Tanimoto係数は実数版とバイナリ*2版があるそうです*3.
バイナリ版Tanimoto係数
こちらは集合の要素の数で計算します.
例えば,
どちらも最小で0,完全一致のとき最大1の範囲をとる係数です.
で,バイナリ版を0と1の要素の配列と考えると実数版でまとめられます.
# 分母は論理積の和,分母の各ベクトルの和は要素が1の二乗の和
コサイン類似度
コサイン角で類似度を表現したものなので,
- 分子 => 「X,Yの内積」
- 分母 => 「XとYのベクトルの大きさの積」
という構成です.
要素の値を正に限ると,最小で0,最大で1の範囲をとることがわかります.
# 0度〜90度のコサイン
比べてみると...
分子が同じ式なので,分母の大きさを比べれば性能がわかりますね.
それぞれの項が正なので,相加相乗平均を考えてみます.
確かめてみる
Ruby で類似度の式を実装して比べてみます*6.
サンプルのデータはScalaで集合知プログラミング その6 から拝借してRubyのhashに書き換えました.
{映画タイトル => { ユーザ => 評価値}, ....}
というデータ構成です.各映画タイトルへの評価はみな同じユーザが行っています.
あとでよく見たらユーザの構成がバラバラでした.
今回検証したプログラムを使うと二乗和部分が不正確で問題ありですが
2つの類似度を比較することには影響はないです.
サンプルデータ
movies = { 'Lady in the Water' => { 'Lisa Rose' => 2.5, 'Jack Matthews' => 3.0, 'Michael Phillips' => 2.5, 'Gene Seymour' => 3.0, 'Mick LaSalle' => 3.0 }, 'Snakes on a Plane' => { 'Jack Matthews' => 4.0, 'Mick LaSalle' => 4.0, 'Claudia Puig' => 3.5, 'Lisa Rose' => 3.5, 'Toby' => 4.5, 'Gene Seymour' => 3.5, 'Michael Phillips' => 3.0 }, 'Just My Luck' => { 'Claudia Puig' => 3.0, 'Lisa Rose' => 3.0, 'Gene Seymour' => 1.5, 'Mick LaSalle' => 2.0 }, 'Superman Returns' => { 'Jack Matthews' => 5.0, 'Mick LaSalle' => 3.0, 'Claudia Puig' => 4.0, 'Lisa Rose' => 3.5, 'Toby' => 4.0, 'Gene Seymour' => 5.0, 'Michael Phillips' => 3.5 }, 'The Night Listener' => { 'Jack Matthews' => 3.0, 'Mick LaSalle' => 3.0, 'Claudia Puig' => 4.5, 'Lisa Rose' => 3.0, 'Gene Seymour' => 3.0, 'Michael Phillips' => 4.0 }, 'You Me and Dupree' => { 'Jack Matthews' => 3.5, 'Mick LaSalle' => 2.0, 'Claudia Puig' => 2.5, 'Lisa Rose' => 2.5, 'Toby' => 1.0, 'Gene Seymour' => 3.5 } }
サンプルデータがHashなので,計算はHashクラスに実装してみます*7.
計算プログラム
class Hash # 二乗和 def sum_square self.inject(0.0){|sum, (k,v)| sum + (v ** 2)} end # 内積 def inner_product(other) self.inject(0.0){|sum, (k,v)| sum + (v * other[k].to_f)} end # 実数Tanimoto係数 def tanimoto(other) inner_product = self.inner_product(other) inner_product / (self.sum_square + other.sum_square - inner_product) end # コサイン類似度 def cosine_similarity(other) self.inner_product(other) / Math.sqrt(self.sum_square * other.sum_square) end end
確認
映画の類似度を総当たりで計算して,コサイン類似度とTanimoto係数の差をとってみました.
(実装に間違いがなければ)やはりコサイン類似度の方が大きい値になっているようです*8.
p movies.inject(Hash::new){|h1, (k1, v1)| h1[k1] = movies.inject(Hash::new){|h2, (k2, v2)| h2[k2] = (v1.cosine_similarity(v2) - v1.tanimoto(v2) >= 0)? true : false h2 } h1 } #=> { "Snakes on a Plane"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true }, "Superman Returns"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true }, "The Night Listener"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true }, "Lady in the Water"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true }, "You Me and Dupree"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true }, "Just My Luck"=>{ "Snakes on a Plane"=>true, "Superman Returns"=>true, "The Night Listener"=>true, "Lady in the Water"=>true, "You Me and Dupree"=>true, "Just My Luck"=>true } }
単純な計算結果
p movies.map{|k1, v1| movies.map{|k2, v2| v1.tanimoto(v2)}} #=> [[1.0, 0.746153846153846, 0.954233409610984, 0.585014409221902, 0.671641791044776, 0.389204545454545], [0.746153846153846, 1.0, 0.767058823529412, 0.661710037174721, 0.667883211678832, 0.519685039370079], [0.954233409610984, 0.767058823529412, 1.0, 0.573604060913706, 0.68, 0.346987951807229], [0.585014409221902, 0.661710037174721, 0.573604060913706, 1.0, 0.689119170984456, 0.39344262295082], [0.671641791044776, 0.667883211678832, 0.68, 0.689119170984456, 1.0, 0.577380952380952], [0.389204545454545, 0.519685039370079, 0.346987951807229, 0.39344262295082, 0.577380952380952, 1.0]] p movies.map{|k1, v1| movies.map{|k2, v2| v1.cosine_similarity(v2)}} #=> [[1.0, 0.864571736660863, 0.979878059993656, 0.815688728171367, 0.876768308983898, 0.702573340933072], [0.864571736660863, 1.0, 0.892170154676181, 0.832995274019294, 0.830515089501848, 0.788386434103751], [0.979878059993656, 0.892170154676181, 1.0, 0.836486445274097, 0.91530229603964, 0.680229773816739], [0.815688728171367, 0.832995274019294, 0.836486445274097, 1.0, 0.816335074333143, 0.581591547095], [0.876768308983898, 0.830515089501848, 0.91530229603964, 0.816335074333143, 1.0, 0.759855876058712], [0.702573340933072, 0.788386434103751, 0.680229773816739, 0.581591547095, 0.759855876058712, 1.0]]
まとめ
- コサイン類似度の方がTanimoto係数より大きい評価値になる.
- 生データが個数のみで構成される場合,バイナリ版Tanimoto係数が使えそう
おまけの収穫
HashもEnumerableだから... とinjectを試してみたところ,
self.inject(0.0){|sum, (k, v)| sum + (v ** 2)}
のように,括弧で括るとキーと値も参照できることがわかったのが収穫でした.
はてなにTeX記法があるのを知ったのも思わぬ収穫!
*1: エクササイズの内容は => http://d.hatena.ne.jp/plasticscafe/20110121/1295582338
*2:0と1のみで構成されるデータ
*3:参考: http://nlp.nagaokaut.ac.jp/tanimoto%E4%BF%82%E6%95%B0
*4:参考: http://www.infra.kochi-tech.ac.jp/takagi/Survey1/13InnerProduct.pdf
*5:詳しい証明はできませんが、ざっくりと.収束速度で比較すればいいのかなぁ?
*6:自分にとって書きやすい言語なので
*7:データ量が多い場合Array使う方が軽快でしょうね.また,Vectorクラスを使えれば内積などは実装しなくてよい....
*8:今思えばUnitTestを利用できればよかったのかも...
コーディング道場でkoan(公案)とkata(型)
海外のIT関係では
ZenやらKan-jiやらNinja! の「東洋の神秘」に妙なリスペクトを集めているような
そんな話をたまに聞くことがあるけれど,
最近はDojo(道場)でkata(型)やkoan(公案)を学ぶスタイルが楽しいらしいのです.
「禅」の雰囲気に技術をストイックに身につけていく姿が見えるのでしょうか?
一つの題材をあれこれ考えて確かめてみるのがとても楽しそうなので,
記事で紹介されていた,
を試してみました.
このサイトで提供されているKoanは"Red, Green, Refactor"
そう,テスト駆動開発なのですよ!
テスト周りの一連の流れがいまいち身に付いていないなーと
感じていた自分にぴったりなKoanなのです.
Test::Unit をベースにしたKoanをちまちまと解いていける,
そんなステキなプログラムなのです*1.
Dojoの門を叩いてみるっ!
おもむろに
$ ruby path_to_enlightenment.rb
と実行してみましょう!*3
するするとテストが走り,
次の問題の箇所が指示されます.
# meditate on the following code:... の箇所.
(in /path/to/working_directory) $rvm_home/bin/ruby path_to_enlightenment.rb AboutAsserts#test_assert_truth has damaged your karma. The Master says: You have not yet reached enlightenment. The answers you seek... Failed assertion, no message given. Please meditate on the following code: /path/to/working_directory/about_asserts.rb:10:in `test_assert_truth' mountains are merely mountains your path thus far [X_________________________________________________] 0/274
指定された箇所のコードを読んで,
テストが通るように書き換えてまた実行...
と繰り返すことで,次のKoan へ進んでいけます*4.
最初はfalseをtrueに書き換えるだけのような簡単な問題ばかりですが,
ちょっとした達成感を積み重ねられるのが楽しい限りです!*5
274題あるようなので,手応えあるボリュームではないでしょうか?
お,と思った方ぜひお試しを!
蛇足
- ついでにGit の操作に慣れる,という課題を設けてもよいかも.
- pythonista にはこんなサイトが