Project Euler - Problem #11 - Ruby
Your Code

Class method

You created the class method parse with this snippet:

class &lt;&lt; self
  def parse str
    Grid.new str.split("\n").map { |row| row.split(" ").map { |val| val.to_i } }
  end
end


That works, but a more conventional definition is just:

def self.parse str
  Grid.new str.split("\n").map { |row| row.split(" ").map { |val| val.to_i } }
end


Further, since self equals Grid when this method is created, it is better to write:

def self.parse str
  new str.split("\n").map { |row| row.split(" ").map { |val| val.to_i } }
end


so that this code needn't be changed if you rename the class. (Note that new str.split... is the same as self.new str.split....)

However, you don't need a class method to create the instance. Instead, just write:

arr = str.split("\n").map { |row| row.split(" ").map { |val| val.to_i } }
grid = Grid.new(arr)


Consolidate all calculations in the class

You want to move:

def max_product lines
  lines.flat_map { |line| line.each_cons(4).to_a }.map { |cons| cons.inject {
   |prd, x| prd * x} }.max
end


and

[:horizontals, :verticals, :diagonals, :inv_diagonals].map { |method|
   max_product(grid.send(method)) }.max


into the class definition and make the latter a method:

def max_product_all_ways
  [:horizontals, :verticals, :diagonals, :inv_diagonals].map { |method|
    max_product(grid.send(method)) }.max
end


but I think it would be clearer to write:

def max_product_all_ways
  [max_product(horizontals),
   max_product(verticals),
   max_product(diagonals),
   max_product(inv_diagonals)].max
end


You would then invoke max_product_all_ways as follows:

arr = str.split("\n").map { |row| row.split(" ").map { |val| val.to_i } }
grid = Grid.new(arr)
grid.max_product_wall_ways


Diagonal detail

It's a small thing, but in diagonals you don't need to compute the diagonals of length less than four if only arrays of size four are to be considered. In fact, you should not, as it is conceivable that a diagonal of length less than four could have the greatest product!

Revised class definition

So this is what we now have (more or less):

class Grid

  def initialize arr
    @matrix = arr
    @dim = @matrix.size
  end

  def max_product_all_ways
    [max_product(horizontals),
     max_product(verticals),
     max_product(diagonals),
     # max_product(inv_diagonals)
     ].max
  end

  private

  def max_product lines
    lines.flat_map { |line| line.each_cons(4).to_a }.
          map { |cons| cons.inject { |prd, x| prd * x} }.max
  end

  def horizontals
    @matrix
  end




  def verticals
    @matrix.transpose
  end

  def diagonals
    (0..@dim-1).map { |i| diag(0, i) }.reverse +
    (1..@dim-1).map { |i| diag(i, 0) }
  end

  def inv_diagonals
    rev.diagonals
  end

  def diag x, y
    result = []
    while x &lt; @dim &amp;&amp; y &lt; @dim
      result &lt;&lt; @matrix[x][y]
      x += 1
      y += 1
    end
    result
  end

  def rev
    @matrix.map { |row| row.reverse }
  end
end


Well, this is still not quite right. The calculation of inv_diagonals is not correct, but it needs a fair bit of work to fix.  I've therefore commented it out in max_product_all_ways.  I also changed the name of the method reverse because it's easily confused with the built-in methods for reverse for strings and arrays.

I'll leave it to you to fix the rest and of course others may offer additional suggestions.

Alternative Solution

Here is one way you could write the program more compactly.

Code

class Grid

  def max_product(a, n)
    [array_max(a, n),
     array_max(a.transpose, n),
     array_max(diagonals(a, n), n),
     array_max(diagonals(rotate(a), n), n)
    ].max
  end

  def array_max(a,n)
    row_max(a.max_by { |r| row_max(r,n) }, n)
  end

  def row_max(r,n)
    r.each_cons(n).max_by { |e| e.reduce(:*) }.reduce(:*)
  end

  def diagonals(a, n)
    (0..a.size-n).each_with_object([]) do |i,b|
      b &lt;&lt; (0...a.size-i).map { |j| a[i+j][j] }
      b &lt;&lt; (0...a.size-i).map { |j| a[j][i+j] } if i &gt; 0
    end
  end

  def rotate(a)
    a.transpose.reverse
  end
end


Examples

Given 20x20 array

a = str.lines.map { |l| l.split.map(&amp;:to_i) }

g.max_product(a,4) #=&gt;    51,267,216
g.max_product(a,3) #=&gt;       776,776
g.max_product(a,5) #=&gt; 3,143,734,308


3x3 array

a = [[1,2,3],
     [4,5,6],
     [7,8,9]]
g.max_product(a,1) #=&gt;   9
g.max_product(a,2) #=&gt;  72
g.max_product(a,3) #=&gt; 504

a = [[1,2,3],
     [4,5,8],
     [7,6,9]]
g.max_product(a,2) #=&gt; 72

a = [[1,7,3],
     [4,8,6],
     [5,2,9]]
g.max_product(a,2) #=&gt; 72

a = [[1,2,3],
     [4,8,6],
     [9,5,7]]
g.max_product(a,2) #=&gt; 72


In the last example, you can see the effects of rotate and diagonals:

b = rotate(a)
  #=&gt; [[3, 6, 7],
  #    [2, 8, 5],
  #    [1, 4, 9]]

c = diagonals(b, 2)
  #=&gt; [[3, 8, 9], [2, 4], [6, 5]]

array_max(c, 2)
  #=&gt; [72, 8, 30].max =&gt; 72


Incidentally, I made the number of elements whose product is to be maximized a variable because it wasn't really any more difficult to do that and it facilitated debugging.