2017-05-09 15 views
3

以下のコードは、2つの異なる結果を生成します。ルビー列挙子チェーンの作業フローの理解方法

letters = %w(e d c b a) 

letters.group_by.each_with_index { |item, index| index % 3 } 
#=> {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]} 

letters.each_with_index.group_by { |item, index| index % 3 } 
#=> {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]} 

実行の流れは右から左であり、データの流れは左から右であると思います。ブロックは、右から左へパラメータとして渡す必要があります。

putsを使用して、このブロックが内部のeachで実行されていることがわかりました。第一鎖中

group_byeachindex%3の結果を返し、group_by結果を処理し、他のブロックにそれを得なければならない、データのためeachを頼むべきです。しかしブロックはどのように通過していますか?ブロックがeachで実行される場合、eachは2つのパラメータitemindexを渡すことはなく、1つのパラメータitemを渡します。

私の理解では、each_with_indexは、最初にeachメソッドからデータを受け取ります。 eachindex%3になる。その場合、どうすればeach_with_indexの処理をindex%3できますか?

何とか間違っているようです。誰かがこの2つの例題を詳細に説明し、そのような場合に一般的な作業フローを与えることができますか?

答えて

0

プロキシは両方実行し、データフローがRubyで任意のメソッド呼び出しと同様に、左から右にある

オブジェクト。

概念的には、Enumeratorのコールチェーンは、右から左に読むのに役立ちます。なぜなら、それらは一種のproxy objectです。

ブロックなしで呼び出されると、どのメソッドが呼び出されたかを覚えているだけです。このメソッドは、実際に呼び出されるのは、たとえばEnumeratorArrayに変換された場合や、要素が画面に表示された場合などです。そのような方法は、チェーンの最後に呼び出されていない場合

、基本的に何も起こらない:

[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index 
# #<Enumerator: ...> 

[1,2,3].each_with_index.each_with_index.each_with_index.each_with_index.to_a 
# [[[[[1, 0], 0], 0], 0], [[[[2, 1], 1], 1], 1], [[[[3, 2], 2], 2], 2]] 

この現象は、メソッド間の巨大なアレイを通過することなく、オブジェクトの非常に大きなストリームで動作することができますコール。出力が必要ない場合、何も計算されません。最後に3つの要素が必要な場合は、3つの要素のみが計算されます。

プロキシパターンが重くActiveRecord::Relationと、例えば、レールに使用される:

@person = Person.where(name: "Jason").where(age: 26) 

なお、この場合、2 DBクエリを起動することは非効率的であろう。しかし、連鎖された方法の終わりにそれを知ることしかできません。ここでは、関連の答えだ(How does Rails ActiveRecord chain “where” clauses without multiple queries?

MyEnumerator

はここで、迅速かつ汚いMyEnumeratorクラスです。

class MyEnumerator < Array 
    def initialize(*p) 
    @methods = [] 
    @blocks = [] 
    super 
    end 

    def group_by(&b) 
    save_method_and_block(__method__, &b) 
    self 
    end 

    def each_with_index(&b) 
    save_method_and_block(__method__, &b) 
    self 
    end 

    def to_s 
    "MyEnumerable object #{inspect} with methods : #{@methods} and #{@blocks}" 
    end 

    def apply 
    result = to_a 
    puts "Starting with #{result}" 
    @methods.zip(@blocks).each do |m, b| 
     if b 
     puts "Apply method #{m} with block #{b} to #{result}" 
     else 
     puts "Apply method #{m} without block to #{result}" 
     end 
     result = result.send(m, &b) 
    end 
    result 
    end 

    private 

    def save_method_and_block(method, &b) 
    @methods << method 
    @blocks << b 
    end 
end 

letters = %w[e d c b a] 

puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.to_s 
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:group_by, :each_with_index] and [nil, #<Proc:[email protected]_enumerator.rb:35>] 
puts MyEnumerator.new(letters).group_by.each_with_index { |_, i| i % 3 }.apply 
# Starting with ["e", "d", "c", "b", "a"] 
# Apply method group_by without block to ["e", "d", "c", "b", "a"] 
# Apply method each_with_index with block #<Proc:[email protected]_enumerator.rb:42> to #<Enumerator:0x00000000e2c610> 
# {0=>["e", "b"], 1=>["d", "a"], 2=>["c"]} 

puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.to_s 
# MyEnumerable object ["e", "d", "c", "b", "a"] with methods : [:each_with_index, :group_by] and [nil, #<Proc:[email protected]_enumerator.rb:48>] 
puts MyEnumerator.new(letters).each_with_index.group_by { |_item, index| index % 3 }.apply 
# Apply method each_with_index without block to ["e", "d", "c", "b", "a"] 
# Apply method group_by with block #<Proc:[email protected]_enumerator.rb:50> to #<Enumerator:0x0000000266b938> 
# {0=>[["e", 0], ["b", 3]], 1=>[["d", 1], ["a", 4]], 2=>[["c", 2]]} 
+0

サンプルコードを実行した後、私はあなたのサンプルコードを実行した後、非常に感謝しています。 [e、d、c、b、a]は、group_byメソッドにリンクされている列挙子を生成します。そのステップでは、列挙子は実行されません。 2番目の適用では、each_index_withを最初の結果(列挙子)に適用すると、each_index_withはその結果を生成します。 '{0 => ["e"、 "b"] ....}'。どのブロックも通過せずに、group_byはどのようにグループ化するのか知っていますか?列挙子から '{0 => ["e"、 "b"] ...}'までのプロセスは神秘的です! – pyb1993

関連する問題