ちょっとわかりにくいけど非常に便利なinjectメソッド

ruby の inject で配列の合計や最小要素を簡単に求められるんだけど、慣れていないと理解しにくい。そこで、できるだけわかりやすく説明したいと思う。

injectの使用例

Ruby初心者が配列の和を求めたいと思ったとき、最初に思いつくのは以下のようなコードだろう。

sum = 0
[1, 2, 3, 4, 5].each do |i|
  sum += i
end
sum                             # => 15

変数sumを用意し、eachで配列の中身を順にsumに足していく。Rubyを知っている人なら誰でも理解できると思う。

これをinjectを使って次のように書ける。

[1, 2, 3, 4, 5].inject(0) { |sum, i| sum + i }   # => 15

簡潔だが、injectを知らないといまいちピンとこないのではないだろうか。ちなみに、Ruby1.8.7以降であれば更に簡単に書ける。

sum = [1, 2, 3, 4, 5].inject(:+)      # => 15

こちらの方がわかりやすいかもしれない。どちらも原理は簡単だ。

injectの原理

Cなどの言語をやったことがあれば、以下の2つが等価だと聞いたことはあるだろう。

1;
while (式2) {
    文
    式3;
}
for (式1; 式2; 式3) {
    文
}

式1が初期化、式2が継続条件、式3が再初期化となる。eachとinjectの関係は、このwhileとforの関係に似ている。そう、まさに上で例として上げた配列の和を求める3つのコードが等価なのだ。

injectにはいくつかの書き方が存在し、その基本となるのがこれ。

ary.inject(init) { |result, item| ... }

これは以下のコードと同じである。

result = init
ary.each do |item|
  result = ...
end

これを理解できれば、和を求めるときは「result + item」を、最大値を求めるときは「(item > result) ? item : result」を 「...」の部分に書けばいいということがわかるのではないだろうか。

もちろん、「result = result + item」は「result += item」と書けるので、最初の2つの例が等価という意味もわかってもらえるだろう。

injectの他の書き方

3つ目の例ではinjectに演算子のシンボルだけを渡している。このように、injectにはいくつか書き方がある。

まずは初期値を省略した場合。

ary.inject { |result, item| ... }

これは、初期値として配列の先頭の値が代入され、2回目のループから開始されると考えればいい。

次に、ブロックなしで演算子のシンボルと初期値をinjectの引数に与えた場合。

ary.inject(init, :[sym])   # [sym] は + や * などの演算子

これは以下と等価になる。

result = init
ary.each do |item|
  result = result [sym] item
end

そしてもう一つが、3つ目の例で挙げた、演算子のシンボルのみをinjectの引数に与えた場合。

ary.inject(:[sym])   # [sym] は + や * などの演算子

これは、ary.inject(init, :[sym]) のinitとして配列の先頭の値が代入され、2回目のループから開始されたときと同じになる。

とりあえず、配列の和を求めたいときは inject(:+) と覚えておいて損はないだろう。

injectの応用例

文字列の長さの最小値、最大値や合計を求める

例えば、配列に入ってる文字列の中で一番長い文字列の長さがほしい場合、injectを使えばこのように書ける。

ary = ["one", "two", "three", "four", "five"]
ary.inject(0) { |max, s| s.length > max ? s.length : max} # => 5

一応、これは下のようにも書ける。

ary.max_by { |s| s.length }.length                        # => 5

ブロックに更にlengthメソッドをつけるのが気持ち悪いので個人的にはinjectが好みだが、まあこの辺は人によるだろうか。

文字列の長さの合計もinjectで一発だ。

ary = ["one", "two", "three", "four", "five"]
ary.inject(0) { |sum, s| sum + s.length }   # => 19
フィボナッチ数列を求める

かの有名なフィボナッチ数列もinjectで書ける。

(0..5).inject([1, 1]) { |fib, i| fib << fib[i] + fib[i+1] } # => [1, 1, 2, 3, 5, 8, 13, 21]

このように、resultに配列やハッシュを使うと応用範囲がかなり広がる。

まとめ

以上、injectメソッドについていろいろ説明してみた。あまりわかりやすい説明ではなかったように思うが、とりあえずfor文が使えたらinjectも使えるはずだということだけは言っておきたかった。

今までinjectを知らなかったり、知っていてもわかりにくいという理由で使っていなかった人も、これからは積極的にinjectを使うことをオススメする。

Rubyでコードリーディングをやってみた

「プログラムを書けるようになるためにはプログラムを読まなければならない」とどこかで聞いたので、Rubyのコードを何か読んでみることにした。

とりあえず初めてのコードリーディングということで、
Ruby/勉強用のお手本になるソースコード - TOBY SOFT wiki
で初心者向けとされている mailread.rb を読むことに。

自分のRubyディレクトリからソースコードを探してみた、が見つからない。調べてみると1.9からなくなったらしい。仕方なくググって見つけたところからコピペ。

コードリーディングと言ったものの、どういうやり方が一番いいのかよくわかってない。コードリーディングのやり方として ひらメソッド というものもあるらしいが、たかが数十行のソースコードにやる意味はなさそうなので、とりあえず読みながらコード中にコメントを挿入していくという方法をとった。

どうせコメントをつけるなら、最近覚えた YARD でドキュメントを出力できるようなコメントにしようということで、コメントを挿入してYARDでドキュメントを出力したのがこれ。

akisute3/code_reading · GitHub

small_programs 内の mailread.rb がコメントを付けたソースコード、doc/ 内が YARD で出力したドキュメントである。ドキュメントはダウンロードして doc/index.html をブラウザで開けば見られると思う。

読んだ感想としては、

  • 引数にgetsが定義されていなければオープンとか「ensure f.close if opened」の辺りは今後プログラムを書くときに使えそう。
  • headerとbodyになんでattr_reader使わないんだろう?

くらい。非常に短いプログラムでも学べるところは十分にあった。

次は 404 Not Found 辺りを読もうかな。上のリンクで中級者以上って書いてるし、Rubyのドキュメント周りについて知りたかったし。

コードリーディングがこれで最後にならなければいいが……

cygwinにruby1.9.3-p0をインストール

今までは RubyInstaller でWindowsにインストールしたものを使っていたが、RSpecの色つけがうまくいかなかったので1.9.2からバージョンアップもかねてcygwinにインストールすることにした。

やることは

$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p0.tar.gz
$ tar zxvf ruby-1.9.3-p0.tar.gz
$ cd ruby-1.9.3-p0/
# ./configure --enable-shared && make && make install

と順に実行するだけ。

ただ、これだけだとgemコマンドを使ったときに

It seems your ruby installation is missing psych (for YAML output).
To eliminate this warning, please install libyaml and reinstall your ruby.

とエラーが表示される。エラー箇所を見てみるとどうやらpsychライブラリが読み込めていないらしい。

Install Ruby 1.9.3 with libyaml on CentOS

には、libyamlをインストールした後にRubyをインストールすると書いているのだが、やってみても同じエラーが出る。

そこでおもむろに以下のコマンドを打ってみた。

$ gem install psych

なんとインストールに成功し、gemコマンドでエラーが表示されなくなった。正しい解決法なのかはわからないが、エラーが表示されなくなりRSpecも色が付いたので、とりあえずよしとしておく。

emacsからはてなダイアリーに記事を投稿できない

simple-hatena-mode
hatena-diary-mode For Emacs

この2つのElispを試してみたのだが、どちらも記事の投稿はできなかった。

simple-hatena-modeは、simple-hatena-binを設定したり、パスの通ったディレクトリにhw.plを配置したりしてみたが「Please answer y or n. `Hatena Diary Writer' not found in hw.pl. Are you sure to continue setup?」と表示される。どうやらhw.plを認識していないようで、投稿キーをおしても「Now posting...」と表示されたまま何も起こらない。

ちなみにはてダラ単体では動作することは確認済み(この記事もhw.plを使用して投稿した)。

hatena-diary-modeは、過去の記事のダウンロードはできるのだが、Emacsで投稿を終えてもブログには反映されない。

長い時間をかけたものの原因はわからなかった。しばらくはEmacsで編集してそれをはてダラで投稿するしかなさそうだ。

とりあえず、.emacsのorg-capture-templatesに以下を書いた(もちろん使用するなら org-mode を導入している必要がある)。

(setq org-capture-templates
      '(("h" "blog" plain
         (file (format-time-string "~/Documents/blog/%Y-%m-%d.txt" (current-time)))
         nil :unnarrowed t)))
(global-set-key (kbd "C-z") 'org-capture)

これで C-z h を押すと ~/Documents/blog ディレクトリ内に 2011-11-17.txt のようなファイルができる。

はてダラを導入し、ディレクトリ内の config.txt に ID とパスワードを書いておけば、できたファイルに記事を書いた後 M-! hw.pl で投稿できる。
txtファイルにorg-captureを使うのも不自然だけど、原因がわかるまではとりあえずこの方法で記事を投稿しようと思う。