映射调查员

时间:2015-07-30 18:47:31

标签: ruby enumerator

在Ruby中使用Enumerator非常简单:

a = [1, 2, 3]
enumerator = a.map
enumerator.each(&:succ) # => [2, 3, 4]

但我可以使用嵌套集合做类似的事情吗?

a = [[1, 2, 3], [4, 5, 6]]
a.map(&:map) # => [#<Enumerator: [1, 2, 3]:map>, #<Enumerator: [4, 5, 6]:map>]

但是现在如何获得[[2, 3, 4], [5, 6, 7]]

这总是可以通过一个块完成:

a = [[1, 2, 3], [4, 5, 6]]
a.map { |array| array.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]

但是我想知道是否有一种避免使用块的方法,部分是因为我觉得必须键入|array| array很烦人,部分因为我很想找到一种方法来做

理想情况下,感觉就像这个伪代码:

a.map.map(&:succ)
# perhaps also something like this
a.map(&:map).apply(&:succ)

2 个答案:

答案 0 :(得分:4)

据我所知,根据您的要求,没有具体的实施方式。

您可以创建一个递归函数来处理它,例如:

def map_succ(a)
  a.map {|arr| arr.is_a?(Array) ? map_succ(arr) : arr.succ}
end

然后无论数组的嵌套程度如何都会有效(如果元素没有响应#succ,这将会失败)。

如果你真的想要,你可以使用monkey_patch数组(无需推荐)

#note if the element does not respond to `#succ` I have nullified it here
class Array 
  def map_succ
    map do |a| 
      if a.is_a?(Array) 
        a.map_succ 
      elsif a.respond_to?(:succ) 
        a.succ
      #uncomment the lines below to return the original object in the event it does not respond to `#succ`
      #else
        #a 
      end
    end
  end
end

示例

a = [[1, 2, 3], [4, 5, 6], [7, 8, 9, [2, 3, 4]], {"test"=>"hash"}, "F"]
a.map_succ
#=> [[2, 3, 4], [5, 6, 7], [8, 9, 10, [3, 4, 5]], nil, "G"]

nil是因为Hash没有#succ方法。

更新

基于此SO Post,可以支持类似的语法,但请注意递归仍然是您最好的选择,这样您就可以支持任何深度而不是明确的深度。

 #taken straight from @UriAgassi's from post above
 class Symbol
   def with(*args, &block)
     ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
   end
 end

然后

 a = [[1,2,3],[4,5,6]]
 a.map(&:map.with(&:succ))
 #=> [[2, 3, 4], [5, 6, 7]]
 a << [7,8,[9,10]] 
 #=> [[2, 3, 4], [5, 6, 7],[7,8,[9,10]]]
 a.map(&:map.with(&:succ))
 #=> NoMethodError: undefined method `succ' for [9, 10]:Array

答案 1 :(得分:4)

我知道这样做的唯一方法是执行以下操作:

a = [[1, 2, 3], [4, 5, 6]]
a.map { |b| b.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]

主要是因为Array#map / Enumerable#mapSymbol#to_proc的组合,您无法将第二个变量传递给#map为其生成的块,从而传递另一个变量到内部#map

a.map(1) { |b, c| c } # c => 1, but this doesn't work :(

所以你来使用块语法; Symbol#to_proc实际上返回一个带有任意数量参数的proc(您可以通过执行:succ.to_proc.arity来测试它,返回-1)。第一个参数用作接收器,接下来的几个参数用作方法的参数 - 这在[1, 2, 3].inject(&:+)中得到证明。但是,

:map.to_proc.call([[1, 2, 3], [4, 5, 6]], &:size) #=> [3, 3]

如何? :map.to_proc创建了这个:

:map.to_proc # => proc { |receiver, *args, &block| receiver.send(:map, *args, &block) }  

然后使用数组数组作为参数调用它,使用此块:

:size.to_proc # => proc { |receiver, *args, &block| receiver.send(:size, *args, &block) }

这会导致.map { |receiver| receiver.size }被有效调用。

这一切都导致了这一点 - 因为#map没有额外的参数,并将它们作为参数传递给块,你必须使用一个块。