映射枚举器

Mapping enumerators

在 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)

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

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

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

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

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

主要是因为Array#map/Enumerable#map and Symbol#to_proc的组合,你不能将第二个变量传递给#map产生的块,从而将另一个变量传递给内部#map

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

所以你可以使用块语法; Symbol#to_proc 实际上是 returns 一个接受任意数量参数的过程(你可以通过 :succ.to_proc.arity,returns -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 不接受额外的参数,并将它们作为参数传递给块,所以你必须使用块。