gonzalo2000
gonzalo2000

Reputation: 648

Changing surrounding elements of an array element

I am a beginner working through some exercises. I'm trying to manipulate a 2-dimensional array such that if an element is a 1, then the surrounding non-diagonal elements should be changed to 1:

[[0,0,0,0],
[0,0,1,0],
[0,0,0,0],
[0,0,0,0]]

should return

[[0,0,1,0],
[0,1,1,1],
[0,0,1,0],
[0,0,0,0]]

I am running into problems using nested each_with_index: After I adjust initial changes for the surrounding left and right, as the method iterates it picks up by earlier adjustment and makes an unwanted change. Moreover, the line which should change the "bottom" element is throwing an error:

a = [[0,0,0,0],
     [0,0,1,0],
     [0,0,0,0],
     [0,0,0,0]
    ]

a.each_with_index do |m, n| # n == index of main array
    m.each_with_index do |x, y| # y == index of subarray
        if x == 1
            a[n][y+1] = 1 unless (a[n][y+1]).nil? #right
            a[n][y-1] = 1 unless (a[n][y-1]).nil? #left
            a[n-1][y] = 1 unless (a[n-1][y]).nil? #top
            a[n+1][y] = 1 unless (a[n+1][y]).nil? #bottom--currently giving an error
        end
    end
end

Any suggestions as to how I can go about solving these two aspects will be well received.

Upvotes: 1

Views: 166

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110665

I suggest you use the Matrix class.

require 'matrix'

m = Matrix[*a]
  #=> Matrix[[0, 0, 0, 0],
  #          [0, 0, 1, 0],
  #          [0, 0, 0, 0],
  #          [0, 0, 0, 0]] 
row, col = m.index(1)
  #=> [1, 2]
Matrix.build(m.row_size, m.column_size) { |r,c|
  (c-col).abs + (r-row).abs <= 1 ? 1 : 0 }.to_a
  #=> [[0, 0, 1, 0],
  #    [0, 1, 1, 1],
  #    [0, 0, 1, 0],
  #    [0, 0, 0, 0]]

The non-matrix version of this (which uses the methods Array#index, Fixnum#divmod, Array::new, Enumerable#each_slice, and a few others) is as follows.

nrows, ncols = a.size, a.first.size
  #=> [4, 4] 
row, col = a.flatten.index(1).divmod(ncols)
  #=> [1, 2] 
Array.new(nrows*ncols) do |i|
  r, c = i.divmod(ncols)
  (c-col).abs + (r-row).abs <= 1 ? 1 : 0
end.each_slice(ncols).to_a
  #=> [[0, 0, 1, 0],
  #    [0, 1, 1, 1],
  #    [0, 0, 1, 0],
  #    [0, 0, 0, 0]]

I find the method using the Matrix class to be easier to understand, though it may not be as efficient.

Upvotes: 1

sawa
sawa

Reputation: 168081

In order to avoid interference of a previous step, you can either (deep) duplicate the array and separate the reference array from the modifying one, or extract all the relevant indices before modifying the array. The latter is better. Also, using a flat array is much easier than handling a nested array, so I will convert a to and from a flattened array b, and work within b.

b = a.flatten

b
.each_index.select{|i| b[i] == 1}
.each do
  |i|
  b[i - 1] = 1 if b[i - 1] and i - 1 >= 0
  b[i + 1] = 1 if b[i + 1]
  b[i - 4] = 1 if b[i - 4] and i - 4 >= 0
  b[i + 4] = 1 if b[i + 4]
end

a = b.each_slice(4).to_a
# => [[0, 0, 1, 0], [0, 1, 1, 1], [0, 0, 1, 0], [0, 0, 0, 0]]

Upvotes: 3

Related Questions