- 集合っぽいもののそれぞれの要素にいろいろできるやつ
- http://docs.ruby-lang.org/ja/2.2.0/class/Enumerable.html
※分類は主観が入ってます
- 写像系
- map
- 折畳み系
- reduce, inject
- each_with_object
- 順番に処理系
- each_with_index
- 探しもの系
- select
- find
- grep
- reject
- 判定系
- member?, include?
- any?
- all?
- 並べ替え系
- sort
- sort_by
- 部分集合系
- take
- drop
- first
- last
- take_while
- drop_while
- 融合系
- zip
Enumerable なクラスを Enumerable なメソッドを使って探してみる。
# 下準備
# クラス階層のトップレベル定義されている定数群
Object.constants # => シンボルの配列が返る
# 定数の実体を取る方法
Object.const_get :String # => String のクラスが返る
# 継承しているクラス or include しているモジュールを調べる
String.ancestors
# => [String, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
# Enumerable なクラスを探す
# トップレベルにある定数の実体
Object.constants
.map {|name| Object.const_get(name) }
# 定数の中からクラスだけを取る
Object.constants
.map {|name| Object.const_get(name) }
.select {|obj| obj.is_a? Class }
# クラス群から Enumerable を include しているものを探す
Object.constants
.map {|name| Object.const_get(name) }
.select {|obj| obj.is_a? Class }
.select {|clazz| clazz.ancestors.include? Enumerable }
# => [Array, Hash, Struct, Range, IO, File, Dir, Enumerator, StringIO, Slop]
- 配列・リストっぽいやつはだいたい Enumerable
- IO もだいたい Enumerable
$stdin.is_a? Enumerable
# => true
$stdin.map(&:upcase)
foo
bar
baz
# => ["FOO\n", "BAR\n", "BAZ\n"]
# 例えばハッシュの場合
hash = {foo: 'bar', hoge: 'fuga'}
hash.map(&:itself)
# => [[:foo, "bar"], [:hoge, "fuga"]]
Hash の場合、[key, value] という形で渡されることが分かる。
(※) Ruby-2.2.x 以上が必要
hash.map do |pair|
# Hash の場合、map のブロックに渡されるのは [key, value] の形なので
key = pair.first
value = pair.last
end
# ↑これを、こう書ける
hash.map do |key, value|
end
# each_with_* メソッドだとこんな感じ
hash.each_with_index do |(key, value), index|
end
- include Enumerable
- each メソッドを実装する
- 以上
class Foo
include Enumerable
def each
# 頑張って実装
end
end
- Enumarable モジュールのメソッドではない
- Enumerable になりたいクラスが実装するもの
- TemplateMethod パターン
インターフェイス仕様はこんな感じ。
- ブロックを渡すと順番に要素を渡してブロックを実行
- ブロックを渡さない場合は Enumerator インスタンスを返す
- 次の値を一つだけ返す
#next
メソッドを持っている - 返す要素が残っていない状態で
#next
を呼ぶと StopIteration 例外
enum = [1,2,3].each
enum.next # => 1
enum.next # => 2
enum.next # => 3
enum.next # StopIteration: iteration reached an end
class Foo
include Enumerable
def each(&block)
return to_enum unless block_given?
# 以下、要素を順にブロックに渡して実行させる実装を頑張る
end
end
class GeometricSequence
include Enumerable
def initialize(first, common_ratio)
@first = first # 初項
@common_ratio = common_ratio # 公比
end
def each(&block)
return to_enum unless block_given?
next_value = @first
while true do
yield next_value
next_value *= @common_ratio
end
end
end
seq = GeometricSequence.new(1, 3)
seq.take(10)
# => [1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]
enum = seq.each
enum.next # => 1
enum.next # => 3
enum.next # => 9
enum.next # => 27
class DirTree
include Enumerable
attr_reader :current_path
def initialize(dir)
@current_path = dir
end
def each(&block)
return to_enum unless block_given?
Dir.new(current_path).each do |entry|
next if entry == '.' || entry == '..'
path = File.join(current_path, entry)
if File.directory?(path)
self.class.new(path).each(&block)
else
yield path
end
end
end
end
DirTree.new('.').map(&:itself)
- 遅延評価
- 必要になるまで値を計算しない
# DirTree での例
# ruby のファイルがありそうな場所を探したい; 最初に見つかったところで ok
DirTree.new('/home/cesare/git')
.select{|name| name =~ /\.rb$/ }
.take(3) # 当分の間、返ってこない
# 遅延評価を使う
DirTree.new('/home/cesare/git').lazy
.select{|name| name =~ /\.rb$/ }
.take(3)
.force # 3件見つかったら速やかに返ってくる