標準添付ライブラリ紹介 【第 5 回】 enumerator

はじめに

この連載について

Ruby 1.8 になって標準添付ライブラリが増えました。そんなライブラリをどんどん紹介していこうという連載です。

今回の記事について

今回は enumerator を紹介します。 ruby 1.9 では既に組み込みとなっていて、 今後はより広く一般的に用いられるようになると思われるライブラリです。

enumerator とは?

Enumerable モジュールに含まれるすべてのメソッドは、 each メソッドを利用して実現されています。

例えば、通常は String#each を用いて行毎にインデックス付きで繰り返す String#each_with_index で、String#each の代わりに String#each_byte を利用してバイト毎に処理してみたいと思ったことはありませんか?

IO.foreach などで each_with_index や collect などの Enumerable モジュールのメソッドを利用したいと思ったことはありませんか?

それを叶えるために enumerator が作られました。

使用例

一番簡単な例は Object#enum_for で each の代わりに使いたいメソッドを指定して、Enumerable モジュールのメソッドを続けるだけです。

コメントに書いてあるものは出力例です。

 require 'enumerator'
 'abc'.enum_for(:each_byte).each_with_index do |e,i|
   puts "#{i}: #{e}"
 end
 #=>
 # 0: 97
 # 1: 98
 # 2: 99

enum_for を使わない場合は、このようになります。

 i = 0
 'abc'.each_byte do |e|
   puts "#{i}: #{e}"
   i += 1
 end

引数を必要とするメソッドでは、enum_for の第 2 引数以降に指定すれば そのままメソッド (下の例では each_slice) に渡されます。

 require 'enumerator'
 [0,1,2,3,4,5].enum_for(:each_slice, 2).each_with_index do |e,i|
   p [e,i]
 end
 #=>
 # [[0, 1], 0]
 # [[2, 3], 1]
 # [[4, 5], 2]

enum_for を使わない場合は、このようになります。 each_slice が enumerator ライブラリで定義されているメソッドなので、 require は残しています。

 require 'enumerator'
 i = 0
 [0,1,2,3,4,5].each_slice(2) do |e|
   p [e,i]
   i += 1
 end

クラスメソッドでも同じように使えます。

 require 'enumerator'
 p IO.enum_for(:foreach, '/etc/hosts').grep(/^127\./)
 #=> ["127.0.0.1\tlocalhost.localdomain\tlocalhost\n"]

enum_for を使わない場合は、このようになります。

 result = []
 IO.foreach('/etc/hosts') do |line|
   if /^127\./ === line
     result.push(line)
   end
 end
 p result

enumerator で追加されるクラスやメソッド

enumerator を require することによって、

  • Enumerable::Enumerator クラス
  • Object#to_enum(method_name = :each, *args)
  • Object#enum_for(method_name = :each, *args)
  • Enumerable#enum_with_index など

が追加されます。

ruby 1.9 (2005-07-15) 以降では組み込みになったため、 require する必要はありません。 *1

Enumerable::Enumerator

使用例では説明を省略していましたが、Object#enum_for(または Object#to_enum)で Enumerable::Enumerator オブジェクトが生成されます。

Enumerable::Enumerator オブジェクトの生成は、以下の 3 通りあります。どれも同じ意味になります。

 enum = obj.enum_for(method_name, *args)
 enum = obj.to_enum(method_name, *args)
 enum = Enumerable::Enumerator.new(obj, method_name, *args)

使用例の例で言うと、それぞれ以下の 3 つずつが同じ意味になります。

 enum1 = 'abc'.enum_for(:each_byte)
 enum1 = 'abc'.to_enum(:each_byte)
 enum1 = Enumerable::Enumerator.new('abc', :each_byte)
 enum2 = [0,1,2,3,4,5].enum_for(:each_slice, 2)
 enum2 = [0,1,2,3,4,5].to_enum(:each_slice, 2)
 enum2 = Enumerable::Enumerator.new([0,1,2,3,4,5], :each_slice, 2)
 enum3 = IO.enum_for(:foreach, '/etc/hosts')
 enum3 = IO.to_enum(:foreach, '/etc/hosts')
 enum3 = Enumerable::Enumerator.new(IO, :foreach, '/etc/hosts')

そして、以下の 2 つが同じ意味になります。

 enum.each { ... }
 obj.method_name(*args) { ... }

上の enum1, enum2, enum3 の例でいうと、 それぞれ以下の 2 つずつが同じ意味になります。

 enum1.each { ...block1... }
 'abc'.each_byte { ...block1... }
 [0,1,2,3,4,5].each_slice(2) { ...block2... }
 enum2.each { ...block2... }
 IO.foreach('/etc/hosts') { ...block3... }
 enum3.each { ...block3... }

Enumerable::Enumerator は Enumerable をインクルードしているので、each の他に Enumerable のメソッドも使えます。

そのため、以下のような書き方が出来ます。 途中の enum1 などの変数を使わずに書くと、 使用例のところに出てきたような書き方になります。

 enum1.each_with_index do |e,i|
   puts "#{i}: #{e}"
 end
 enum2.each_with_index do |e,i|
   p [e,i]
 end
 p enum3.grep(/^127\./)

Enumerable に追加されるメソッド

enumerator を require すると、 以下のメソッドが Enumerable に追加されます。

  • enum_ 系
    • Enumerable#enum_cons(n)
    • Enumerable#enum_slice(n)
    • Enumerable#enum_with_index
  • each_ 系
    • Enumerable#each_cons(n) {...}
    • Enumerable#each_slice(n) {...}

enum_ が頭についているメソッドは Enumerable::Enumerator を生成するメソッドになります。

each_ が頭についているメソッドは each と同じように繰り返すメソッドで、Enumerable::Enumerator とは直接の関係はないメソッドですが、 あると便利なので enumerator で定義されています。

each_slice と each_cons は、どちらも n 要素ずつ繰り返すメソッドです。 違いは言葉で説明するよりも、以下の使用例を見た方がわかりやすいと思います。

 require 'enumerator'
 (1..7).each_cons(5) {|x| p x}
 #=>
 # [1, 2, 3, 4, 5]
 # [2, 3, 4, 5, 6]
 # [3, 4, 5, 6, 7]
 (1..7).each_slice(3) {|x| p x}
 #=>
 # [1, 2, 3]
 # [4, 5, 6]
 # [7]
 (1..7).enum_cons(5).each_with_index {|x,i| p [x,i]}
 #=>
 # [[1, 2, 3, 4, 5], 0]
 # [[2, 3, 4, 5, 6], 1]
 # [[3, 4, 5, 6, 7], 2]
 (1..7).enum_slice(3).each_with_index {|x,i| p [x,i]}
 #=>
 # [[1, 2, 3], 0]
 # [[4, 5, 6], 1]
 # [[7], 2]
 ['a', 'b', 'c'].enum_with_index.select {|x,i| p x if i%2==0}
 #=>
 # "a"
 # "c"

終わりに

今回は便利そうなのに意外と使われていないような気がする enumerator を紹介してみました。

著者について

西山和広。 Ruby hotlinks 五月雨版Ruby リファレンスマニュアル のメンテナをやっています。 Ruby リファレンスマニュアル はいつでも執筆者募集中です。 何かあれば、マニュアル執筆編集に関する議論をするためのメーリングリスト rubyist@freeml.com(参加方法)へどうぞ。

更新日時:2005/11/14 18:25:54
キーワード:
参照:[Rubyist Magazine 0011 号] [0011 号 巻頭言] [各号目次] [pre-BundledLibrariesPlan] [prep-0011]

*1 互換性のために $LOADED_FEATURES に "enumerator.so" が入っているので、require しても問題はありません。