SerenityNow13
SerenityNow13

Reputation: 3

error when searching through 2d array ruby

I have the following grids (connect four)

grid1 = [
  [nil, nil, nil],
  [1, nil, nil],
  [1, nil, nil],
  [1, nil, nil]
]
grid2 = [
  [nil, nil, nil],
  [nil, nil, 1],
  [nil, nil, 1],
  [nil, nil, 1]
]

grid3 = [
  [nil, nil, nil],
  [nil, nil, nil],
  [nil, nil, nil],
  [1, 1, 1]
]

and this is the method I created to find three 1's in a vertical row and return the next available slot above

def searchArray(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      if array[yi][xi] != nil && array[yi][xi] == array[yi+1][xi] && array[yi][xi] == array[yi+2][xi]
        return v = [yi-1, xi]
      end
    end
  end
end


searchArray(grid2)

When I call the method on grid1, and grid 2 it works great but when I call it on Grid 3 the grid where the 1's are placed on the bottom row I get this error

undefined method `[]' for nil:NilClass
(repl):28:in `block (2 levels) in searchArray'
(repl):27:in `each'
(repl):27:in `each_with_index'
(repl):27:in `block in searchArray'
(repl):26:in `each'
(repl):26:in `each_with_index'
(repl):26:in `searchArray'
(repl):36:in `<main>'

Not sure what's going on Thanks

Upvotes: 0

Views: 58

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Let's take a look at your code, simplified slightly1:

def search_array(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      return [yi-1, xi] if x != nil && x == array[yi+1][xi] && x == array[yi+2][xi]
    end
  end
end

You go one row at a time, then for each element in that row, check if that element is not nil and if so, determine whether the two elements below it have the same non-nil value. If you reach the penultimate (next-to-last) row, yi = array.size - 2, you will compare x with array[yi+2][xi], which equals array[array.size][xi], which in turn equals nil[xi]. However, nil has no method [] so an undefined method exception is raised. Pay close attention to those error messages; often, as here, they guide you to the error.

Another problem is that if you found 1's in the first three rows of a column j you would return the index [-1, j], -1 being 0-1. You don't want that either.

I understand that you also wish to determine if dropping a coin in a column results in four-in-a-row horizontally. You could check both vertically and horizontally as follows.

def search_array(arr)
  arr.first.each_index do |j|
    r = arr.each_index.find { |i| arr[i][j] == 1 }
    next if r == 0
    r = r.nil? ? arr.size-1 : r-1
    return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)
  end
  nil
end

def below?(arr,r,j)
  r < arr.size-3 && (1..3).all? { |i| arr[r+i][j] == 1 }
end

def right?(arr,r,j)
  j < arr.first.size-3 && (1..3).all? { |i| arr[r][j+i] == 1 }
end

def left?(arr,r,j)
  j >= 3 && (1..3).all? { |i| arr[r][j-i] == 1 }
end

grid4 = [
  [nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil],
  [nil, nil,   1, nil, nil],
  [nil, nil,   1,   1,   1],
  [  1,   1,   1, nil,   1]
]

grid5 = [
  [nil, nil, nil, nil, nil],
  [nil, nil, nil, nil, nil],
  [nil, nil,   1, nil, nil],
  [nil,   1,   1, nil, nil],
  [nil,   1,   1, nil,   1]
]

search_array grid1 #=> [0, 0] (vertical)
search_array grid2 #=> [0, 2] (vertical)
search_array grid3 #=> nil
search_array grid4 #=> [3, 1] (horizontal)
search_array grid5 #=> [1, 2] (vertical)

Note that if you wish to also check for four-in-a-row diagonnal you could change:

return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j)

to

return [r,j] if below?(arr,r,j) || left?(arr,r,j) || right?(arr,r,j) ||
  top_left_to_bottom_right?(arr,r,j) || bottom_left_to_top_right?(arr,r,j)

and add the additional methods top_left_to_bottom_right? and bottom_left_to_top_right?.

1. I changed the name of your method to search_array because Ruby has a convention to use snake case for the naming of variables and methods. You don't have to adopt that convention but 99%+ of Rubiests do.

Upvotes: 0

iGian
iGian

Reputation: 11193

I could suggest a slight different approach, this is not a complete solution, just a start. It should also help to catch the four.

First map the not nil indexes of the grid, let's consider grid3:

mapping = grid3.flat_map.with_index{ |y, yi| y.map.with_index { |x, xi| [xi, yi] if x }.compact }
#=> [[0, 3], [1, 3], [2, 3]]

Then group by first and second element to get the columns and rows:

cols = mapping.group_by(&:first) #=> {0=>[[0, 3]], 1=>[[1, 3]], 2=>[[2, 3]]}
rows = mapping.group_by(&:last) #=> {3=>[[0, 3], [1, 3], [2, 3]]}

Now, if you want to look for three elements in a row or in a column:

cols.keep_if { |_,v| v.size == 3 } #=> {}
rows.keep_if { |_,v| v.size == 3 } #=> {3=>[[0, 3], [1, 3], [2, 3]]}

The first line says there are no columns with three element aligned. The second line says that row with index 3 has three elements aligned and indexes are [[0, 3], [1, 3], [2, 3]].


Next step it to check that there are no gaps amongst elements. For example in a 4x4 grid you could get also [[0, 3], [1, 3], [3, 3]] which are three elements, but there is a gap in [2, 3],

Upvotes: 0

tadman
tadman

Reputation: 211600

You can solve a lot of problems here by simplifying this code using dig:

def search_array(array)
  array.each_with_index do |y, yi|
    y.each_with_index do |x, xi|
      stack = (0..2).map { |o| array.dig(yi + o, xi) }

      if (stack == [ 1, 1, 1 ])
        return [ yi - 1, xi ]
      end
    end
  end
end

Where dig can poke around and not cause exceptions if it misses the end of the array. Here map is used to quickly pull out an N high stack. You can do 1..2 or 0..4 or whatever is necessary.

Upvotes: 1

Related Questions