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)

      

+3


source to share


2 answers


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.

+4


source


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

      

+4


source







All Articles