2

Is there an elegant way to find and replace any integers superior to 3 (as example) in a multidimensional array? The array may have the dimension 1, 2, 3, or more. Just an example of a such array:

[ [ [ 3, 3, 5 ], 
    [ 4, 3, 3 ] ], 
  [ [ 3, 2, 3 ], 
    [ 0, 3, 8 ] ] ]

I would like to do so without flatten the array.

1
  • With the number 3 (because it's the maximum needed value in the example). Commented May 21, 2011 at 8:09

3 Answers 3

5

Following sepp2k idea, here is a possible implementation:

class Object
  def deep_map(&block)
    if self.respond_to? :each
      result = []
      self.each do |e|
        result << e.deep_map(&block)
      end
      return result
    else
      return block.call(self)
    end
  end  
end

Then apply deep_map as you wish on the array:

> [[[3, 3, 5], [4, 3, 3]], [[3, 2, 3], [0, 3, 8]]].deep_map { |e| e > 3 ? 0 : e }
=> [[[3, 3, 0], [0, 3, 3]], [[3, 2, 3], [0, 3, 0]]] 

Or, more briefly:

class Object
  def deep_map(&block)
    respond_to?(:map) ? map{|e| e.deep_map(&block)} : block.call(self)
  end
end

Or, polymorphically:

class Object
  def deep_map(&block); block.call(self) end
end

class Array
  def deep_map(&block); map{|e| e.deep_map(&block)} end
end
Sign up to request clarification or add additional context in comments.

6 Comments

@i-blis, I've added a shorter version of what you wrote, if you want it.
each after map in the shorter version should be removed.
You can also do it polymorphically, as I added.
Thanks sawa. The shorter version is even more readable. The polymorphic solution is pretty elegant. I do not see this often in Ruby code – is this a popular coding idiom?
I would say no, the polymorphic solution isn't a common idiom in Ruby. In Ruby, it's more common to use duck-typing. In other words, an object may respond to map but not be an Array, in which case the "brief" version would work but the polymorphic one wouldn't.
|
2

You can write a deep_map method, which calls map on the array and then for each element test whether it's a sub-array. If it is, call deep_map recursively with the sub-array, otherwise yield the element.

You can then use that deep_map method to transform the inner elements of your multi-dimensional array without affecting its structure.

Comments

0

So, if I've done this right, f(x) will traverse a multidimensional Array until it finds one containing something that isn't an Array or subclass of Array, at which point it will yield the innermost Array to the block and replace it with the block's return value.

def f x, &block
  x.map do |a|
    if a.first.class.ancestors.include? Array
      f a, &block
    else
      yield a
    end
  end
end

p(f [ [ [ 3, 3, 5 ],
        [ 4, 3, 3 ] ],
      [ [ 3, 2, 3 ],
        [ 0, 3, 8 ] ] ] do |e|
    e.reject { |x| x > 3 }
  end
)

1 Comment

Thank you, @DigitalRoss. Your code is very clean. But, because my question wasn't very clear, I would like to keep the array structure. Anyway, thanks again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.