在迭代器上调用块方法:each.magic.collect {...}

时间:2010-04-07 09:50:34

标签: ruby iterator

我有一个带有自定义每个方法的类:

class CurseArray < Array
    def each_safe
        each do |element|
            unless element =~ /bad/
                yield element
            end
        end
    end 
end

并希望调用不同的块方法,例如“收集”或“注入”这些迭代元素。例如:

curse_array.each_safe.magic.collect {|element| "#{element} is a nice sentence."}

我知道有一个特定的功能(我在这里称之为“魔法”)来做到这一点,但我已经忘记了。请帮忙! : - )

3 个答案:

答案 0 :(得分:6)

如果方法产生,则需要将块传递给它。无法定义自动传递的块。

最贴近我可以达到你的规格:

def magic(meth)
  to_enum(meth)
end

def test
  yield 1 
  yield 2
end

magic(:test).to_a
# returns: [1,2]

实施请求的最简洁方法可能是:

class MyArray < Array 
  def each_safe 
    return to_enum :each_safe unless block_given? 
    each{|item| yield item unless item =~ /bad/}
  end 
end

a = MyArray.new 
a << "good"; a << "bad" 
a.each_safe.to_a
# returns ["good"] 

答案 1 :(得分:2)

您编写each_safe方法的方式最简单的方法是

curse_array.each_safe { |element| do_something_with(element) }

编辑:哦,你的each_safe方法也不正确。它必须是“每个人”,而不是“each.do”

编辑2:如果您真的希望能够执行“each_safe.map”之类的操作,同时还可以执行“each_safe { ... }”您可以写这样的方法:

require 'enumerator'

class CurseArray < Array
  BLACKLIST = /bad/
  def each_safe
    arr = []
    each do |element|
      unless element =~ BLACKLIST
        if block_given?
          yield element
        else
          arr << element
        end
      end
    end

    unless block_given?
      return Enumerator.new(arr)
    end
  end
end

答案 2 :(得分:0)

选定的解决方案使用了常见的习惯用语to_enum :method_name unless block_given?,但也有其他选择:

  1. 保持“不友好”的yielder方法不变,请在调用时使用enum_for

  2. 使用惰性Enumerator

  3. 使用延迟数组(需要Ruby 2.0或gem enumerable-lazy)。

  4. 这是一个演示代码:

    class CurseArray < Array
      def each_safe
        each do |element|
          unless element =~ /bad/
            yield element
          end
        end
      end 
    
      def each_safe2
        Enumerator.new do |enum|
          each do |element|
            unless element =~ /bad/
              enum.yield element
            end
          end
        end
      end 
    
      def each_safe3
        lazy.map do |element|
          unless element =~ /bad/
            element
          end
        end.reject(&:nil?)
      end 
    end
    
    xs = CurseArray.new(["good1", "bad1", "good2"])
    xs.enum_for(:each_safe).select { |x| x.length > 1 }
    xs.each_safe2.select { |x| x.length > 1 }
    xs.each_safe3.select { |x| x.length > 1 }.to_a