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

4

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

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

我想在这些迭代的元素上调用不同的代码块方法,如“collect”或“inject”。例如:

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

我知道有一个特定的函数(我在这里称之为“magic”)可以做到这一点,但我已经忘记了。请帮忙! :-)
3个回答

6

如果一个方法使用yield关键字,你需要传递一个块作为参数。没有一种方式可以自动传递块本身。

根据您的要求,我能够最接近您的规范:

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"] 

2
你编写each_safe方法的方式,最简单的方法是:
curse_array.each_safe { |element| do_something_with(element) }
编辑:哦,你的each_safe方法也不正确。它必须是"each do",而不是"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

谢谢。我本来想用=~进行正则表达式匹配。你描述的方法是我现在使用的方法,但不太优雅。 - blinry
@blinry你使用=〜Regexp是正确的,因为你可能想在黑名单中加入更多单词。@dominikh让each_safe可以有或没有块的方法是一个好方法。 - James A. Rosen
1
构建答案然后返回 Enumerator 是不好的想法。Enumerator 的整个理念就是要进行惰性求值!如果在调用 each_safe 之后但在使用枚举器之前修改了数组会发生什么?如果它是一段长文本,我们只需要 first(3) 呢?请查看下面的 Sam 回答。 - Marc-André Lafortune

0

所选解决方案使用了常见的习语to_enum:method_name unless block_given?这是可以的,但有替代方案:

  1. 保留您的“不友好”的yielder方法,调用它时使用enum_for

  2. 使用lazy Enumerator

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

这是一个演示代码:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接