Displaying counters
Usage Enumerator
in Ruby is pretty simple:
a = [1, 2, 3]
enumerator = a.map
enumerator.each(&:succ) # => [2, 3, 4]
But can I do something similar with nested collections?
a = [[1, 2, 3], [4, 5, 6]]
a.map(&:map) # => [#<Enumerator: [1, 2, 3]:map>, #<Enumerator: [4, 5, 6]:map>]
But how do I get it [[2, 3, 4], [5, 6, 7]]
?
This can always be done with a block:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |array| array.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
But I was wondering if there was a way to avoid using the block, partly because I find what to type |array| array
, and also partly because I am curious to find a way to do it.
Ideally, it would be like this psuedocode:
a.map.map(&:succ)
# perhaps also something like this
a.map(&:map).apply(&:succ)
source to share
The only way I know is to do the following:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |b| b.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
Mainly due to the combination of Array#map
/ Enumerable#map
and Symbol#to_proc
, you cannot pass a second variable to the block it #map
is issued for, and thus pass another variable to the inner one #map
:
a.map(1) { |b, c| c } # c => 1, but this doesn't work :(
So, you have to use block syntax; Symbol#to_proc
actually returns a proc, which takes any number of arguments (you can check this by doing :succ.to_proc.arity
that, which returns -1
). The first argument is used as the sink, and the next few arguments are used as method arguments, as shown in [1, 2, 3].inject(&:+)
. However
:map.to_proc.call([[1, 2, 3], [4, 5, 6]], &:size) #=> [3, 3]
How? :map.to_proc
creates the following:
:map.to_proc # => proc { |receiver, *args, &block| receiver.send(:map, *args, &block) }
Then called with an array of arrays as an argument with this block:
:size.to_proc # => proc { |receiver, *args, &block| receiver.send(:size, *args, &block) }
This leads to an effective challenge .map { |receiver| receiver.size }
.
All this leads to this, since #map
it takes no additional arguments and passes them to the block as parameters, you need to use the block.
source to share
As far as I know, there is no specific implementation as you requested it.
You could just create a recursive function to handle this, like this:
def map_succ(a)
a.map {|arr| arr.is_a?(Array) ? map_succ(arr) : arr.succ}
end
Then it will work no matter how deeply nested the array is (caveat, if the elements don't respond to #succ
, this won't work).
If you really wanted, you could monkey_patch Array (NO WAY RECOMMENDED)
#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
Example
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
is that Hash
it has no method #succ
.
UPDATE
Based on this SO Post it is possible to support similar syntax, but note that recursion is still probably the best for you so that you can support any depth rather than an explicit one.
#taken straight from @UriAgassi from post above
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
Then
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
source to share