映射枚举器
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
不接受额外的参数,并将它们作为参数传递给块,所以你必须使用块。
在 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
不接受额外的参数,并将它们作为参数传递给块,所以你必须使用块。