Thoughts of an Eaten Sun

through the mind of kyle tolle

Deep Each in Ruby

I’ve got a project where I’m using a multidimensional array to represent a grid. It’s conceptually simple. The grid would look something like this:

--------------------------
|  1 |  2 |  3 |  4 |  5 |
|  6 |  7 |  8 |  9 | 10 |
| 11 | 12 | 13 | 14 | 15 |
--------------------------

Effectively, the single outer array has three inner arrays.

two_d_grid =
  [
    [ 1,   2,   3,   4,    5 ],
    [ 6,   7,   8,   9,  10 ],
    [ 11, 12, 13, 14, 15]
  ]

My goal is to iterate through each cell in this 2D grid and process the cell. And that could be done like so:

two_d_grid.each do |row|
  row.each do |cell|
    puts "Cell: #{cell}"
  end
end

But what about a 3D grid?

three_d_grid =
  [
    [
      [ 1,  2,  3],
      [ 4,  5,  6]
    ],
    [
      [ 7,  8,  9],
      [10, 11, 12]
    ],
    [
      [13, 14, 15],
      [16, 17, 18]
    ]
  ]

You could think of this as stacking three planes, one behind the other.

The first plane is closest toward you.

----------------
|  1 |  2 |  3 |
|  4 |  5 |  6 |
----------------

With the next plane right behind it.

----------------
|  7 |  8 |  9 |
| 10 | 11 | 12 |
----------------

and the last plane is the one that’s furthest back.

----------------
| 13 | 14 | 15 |
| 16 | 17 | 18 |
----------------

To traverse this array and get each cell, you have to do things a bit differently.

three_d_grid.each do |plane|
  plane.each do |row|
    row.each do |cell|
      puts "Cell: #{cell}"
    end
  end
end

For every dimension you add to the array, you have to add another nested each call. This isn’t very extensible. Not to mention, this approach doesn’t work at all for a data structure that’s unevenly nested.

uneven_dimensional_grid =
  [
    1,
    [2, 3, 4],
    [
      [5, 6, 7],
      [8, 9, 10]
    ],
    11,
    [
      [
        [12],
        13
      ],
      14
    ],
    15
  ]

What I’d really like in an extensible way to iterate through any array and return each cell, regardless of how deeply nested it is.

I’ve lost the original link, and this code is a bit different, but I came across a way to do this using a lamba.

def deep_each(object, &block)
  traverser = lambda do |obj|
    if obj.respond_to?(:each)
      obj.each(&traverser)
    else
      block.call obj
    end
  end

  traverser.call object
end

This method sets up a lambda. This lambda checks whether the object it’s called with responds to each. If that object does, the lambda calls each, passing in the outer block. If the object doesn’t, the lambda calls the block with that object itself.

Now you can deeply iterate over any any object, if it supports it. And if it doesn’t, nothing blows up.

This will iterate over the 2D grid:

deep_each(two_d_grid) do |cell|
  puts "Cell: #{cell}"
end

As well as the 3D grid:

deep_each(three_d_grid) do |cell|
  puts "Cell: #{cell}"
end

And even that uneven multidimensional array:

deep_each(uneven_dimensional_grid) do |cell|
  puts "Cell: #{cell}"
end

If you wanted to monkey-patch Enumerable(be careful!), you can do that too.

module Enumerable
  def deep_each(&block)
    traverser = lambda do |obj|
      if obj.respond_to?(:each)
        obj.each(&traverser)
      else
        block.call obj
      end
    end

    traverser.call self
  end
end

Then you could call it like so

uneven_dimensional_grid.deep_each do |cell|
  puts "Cell: #{cell}"
end

Use this method to iterate over a data structure and process each of the objects contained within it.

Grab the code and give it a test yourself.

Have any feedback about this? Let me know!

Hack on!