This is my array:
[[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]]
for which I would like the '0' cells immediately above, below, to the right and left of the 1's to change into 1's as well.
The expected output would be:
0100
1111
0111
0001
However, with my code:
class Image
def initialize(image)
#image = image
end
def output_image
#image.map do |image|
puts image.join('')
end
end
def blur
find_ones.each do |x, y|
blur_cell x, y
end
end
def find_ones
ones = []
#image.each_with_index do |row, y|
row.each_with_index do |cell, x|
ones << [x, y] if cell == 1
end
end
ones
end
def blur_cell(x, y)
write_cell x + 1, y, 1
write_cell x - 1, y, 1
write_cell x, y + 1, 1
write_cell x, y - 1, 1
end
def write_cell(x, y, value)
return nil unless y > 0 && y < #image.length
return nil unless x > 0 && x < #image[0].length
#image[y][x] = value
end
end
image = Image.new([
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
])
image.blur
image.output_image
I am getting this output:
0000
0111
0111
0001
Any help with pointing me to where my error is or any advice on how to fix it would be appreciated :)
Errors in Your Code
Your code had a few small errors. Below is the corrected code. Compare that with your original code, line-by-line, and you'll see the errors and how I've fixed them. I've also made a few simplifications.
class Image
def initialize(image)
#image = image
end
def output_image
#image.map do |image|
puts image.join('')
end
end
def blur
find_ones.each do |x, y|
blur_cell x, y
end
end
def find_ones
ones = []
#image.each_with_index do |row, x|
row.each_with_index do |cell, y|
ones << [x, y] if cell == 1
end
end
ones
end
def blur_cell(x, y)
write_cell x + 1, y
write_cell x - 1, y
write_cell x, y + 1
write_cell x, y - 1
end
def write_cell(x, y)
return unless y >= 0 && y < #image.length
return unless x >= 0 && x < #image[0].length
#image[x][y] = 1 # was reversed
end
end
image = Image.new([
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
])
image.blur
image.output_image
#=> 0100
# 1111
# 0111
# 0001
Suggested alternative
Here's another way of doing that.
def convert(arr)
return [] if arr.empty?
nbr_rows = arr.size
nbr_cols = arr.first.size
a = container(arr)
(1..nbr_rows).
each_with_object(Array.new(nbr_rows) { Array.new(nbr_cols) }) { |i,b|
(1..nbr_cols).each { |j|
b[i-1][j-1] = [a[i][j], a[i][j-1], a[i][j+1], a[i-1][j], a[i+1][j]].max } }
end
def container(arr)
nbr_rows = arr.size
nbr_cols = arr.first.size
Array.new(nbr_rows+2) { |i|
Array.new(nbr_cols+2) { |j| (i.zero? || i==nbr_rows+1 || j.zero? ||
j==nbr_cols+1) ? 0 : arr[i-1][j-1] } }
end
Example
arr = [
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
]
convert arr
#=> [[0, 1, 0, 0],
# [1, 1, 1, 1],
# [0, 1, 1, 1],
# [0, 0, 0, 1]]
Explanation
First observe that an element that equals 0 is set to 1 if the element above, below, left or right equals 1. For elements that are not in the first or last row or first or last column, the calculation is straightforward. One way of dealing with elements on the perimeter is construct a second array that starts with the original array and adds rows of zeros before and aft, and rows of zeros to the left and right. Computations are then made for all elements other than the perimeter rows and columns. Lastly, the first and last rows and first and last columns are stripped away. That's what I've done,
The steps are as follows for the array used in the example. First consider the method container.
nbr_rows = arr.size
#=> 4
nbr_cols = arr.first.size
#=> 4
Array.new(nbr_rows+2) { |i|
Array.new(nbr_cols+2) { |j| (i.zero? || i==nbr_rows+1 || j.zero? ||
j==nbr_cols+1) ? 0 : arr[i-1][j-1] } }
#=> Array.new(6) { |i|
# Array.new(6) { |j| (i.zero? || i==5 || j.zero? ||
# j==5) ? 0 : arr[i-1][j-1] } }
#=> [[0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0],
# [0, 0, 1, 0, 0, 0],
# [0, 0, 0, 0, 1, 0],
# [0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0]]
Notice that this array is arr sandwiched between two rows of zeroes and two columns of zeroes.
Now let's step through convert.
arr.empty?
#=> false, so we do not return []
nbr_rows = arr.size
#=> 4
nbr_cols = arr.first.size
#=> 4
a = container(arr)
#=> [[0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0],
# [0, 0, 1, 0, 0, 0],
# [0, 0, 0, 0, 1, 0],
# [0, 0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0, 0]]
(1..nbr_rows).each_with_object(Array.new(nbr_rows) { Array.new(nbr_cols) }) { |i,b|
(1..nbr_cols).each { |j|
b[i-1][j-1] = [a[i][j], a[i][j-1], a[i][j+1], a[i-1][j], a[i+1][j]].max } }
#=> (1..4).each_with_object(Array.new(4) { [0,0,0,0] }) { |i,b|
# (1..4).each { |j|
# b[i-1][j-1] = [a[i][j], a[i][j-1], a[i][j+1], a[i-1][j], a[i+1][j]].max } }
#=> [[0, 1, 0, 0],
# [1, 1, 1, 1],
# [0, 1, 1, 1],
# [0, 0, 0, 1]]
For readers unfamiliar with Enumerable#each_with_object, the last expression is effectively the same as the following three lines.
b = Array.new(nbr_rows) { Array.new(nbr_cols) }
(1..nbr_rows).each { |i|
(1..nbr_cols).each { |j|
b[i-1][j-1] = [a[i][j], a[i][j-1], a[i][j+1], a[i-1][j], a[i+1][j]].max } }
b
For a Ruby solution, please see #CarySwoveland's answer.
Since your class is called Image and your method name is blur_cell, you might want to check Image Magick and MiniMagick.
The transformation you're looking for is called "diamond dilation".
With ImageMagick, it's as easy as :
convert matrix.png -morphology Dilate Diamond dilated_matrix.png
It converts
to
To convert a matrix to bitmap and vice-versa, you can use
this gem to read and write .pbm files.
Related
Scenario : I want to count no. of subsets having negative numbers between two zeros. Here no. of subsets with negative numbers can be consider as travelling down the hill from sea level
Consider a array
a=[0,1,0,-1,-2,-l,0]
0 here represent sea level. Numbers between zeros are steps going up/down and is represent by positive/negative consecutive steps.
You can move any steps up/down but it should come to sea level before moving up/down
If you consider
0,1,0 then it means that you moved from sea level to uphill and then again to sea level
0, 1, 0
similarly
From sea level to moving down one step, second step down, one step up and then again to sea level
0, -1, -2, -1, 0
Now considering an example.
a=[0,1,0,-1,-2,-1,0]
subset #1 = [0,1]
subset #2 = [0,-1,-2,-1,0]
Output: 1 (One time travelling below sea level)
Consider another example
b = [0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0]
subset #1 = [0,1,2,1,0] (moving up)
subset #2 = [0,-1,-2,-1,0] (moving down)
subset #3 = [0,1,2,1,0] (moving up)
subset #4 = [0,-1,0] (moving down)
Output: 2 (Two times travelling below sea level)
I'm wondering if there's a reason that you couldn't just count the times that you have a zero followed by a negative number; something like:
def count_below(arr)
count = 0
arr.each_index do |i|
count += 1 if arr[i] == 0 && (arr[i + 1] || 0) < 0
end
count
end
irb(main):039:0> count_below([0,1,0,-1,-2,-1,0])
=> 1
irb(main):040:0> count_below([0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0])
=> 2
Option 1: Produce Desired "Output" (Number of times below "Sea Level")
Assumptions:
We start at or above Sea Level.
Proposed Solution:
b = [0,1,2,1,0,-1,-2,-1,0,1,2,1,0,-1,0]
b.each_cons(2).count {|a,b| !a.negative? && b.negative? }
Steps:
Create an Enumerator of consecutive 2 elements (b.each_cons(2))
Count each time the first element is not negative and the second is negative (count {|a,b| !a.negative? && b.negative? })
Option 2: To produce the slices in your question the following should work
Assumptions:
We always start at Sea Level (0)
One must touch Sea Level when ascending or descending (e.g. [0,1,-1] is considered invalid input)
The first example is incorrect and should be [[0,1,0],[0,-1,-2,-1,0]]
Proposed Solution:
b.each_cons(2).with_object([]) do |(a,b),obj|
obj << [a] if a.zero?
obj.last << b
end
#=> [[0, 1, 2, 1, 0], [0, -1, -2, -1, 0], [0, 1, 2, 1, 0], [0, -1, 0]]
Steps:
Create an Enumerator of consecutive 2 elements (b.each_cons(2))
Iterate with a Accumulator Array (with_object([]))
Every time the first element is 0 insert a new sub Array (obj << [a] if a.zero?)
Insert the second element into the current (last) sub Array (obj.last << b)
with_object will return the accumulator Array as the result
You could chain methods to get the same result as Option 1 (such as count {|a| a.any?(&:negative)})
My solution assumes that you wish to count the number of sub-arrays that begin and end with zero and from the initial zero monotonically decrease to a minimum (negative) value, then monotonically increase to the final zero.
def count_em(arr)
arr.flat_map.with_index { |e,i| (e.zero? && i > 0 && i < arr.size-1) ? [0,0] : [e] }
.slice_when { |*pair| pair == [0,0] }
.count { |a| downhill?(a) }
end
def downhill?(arr)
return false if arr[1] > 0
imin = arr.each_index.min_by { |i| arr[i] }
return false unless decreasing?(arr[0..imin])
decreasing?(arr[imin..-1].reverse)
end
def decreasing?(arr)
arr.each_cons(2).all? { |e,f| f < e }
end
count_em [0, 1, 0, -1, -2, -1, 0]
#=> 1
count_em [0, 1, 2, 1, 0, -1, -2, -1, 0, 1, 2, 1, 0, -1, 0]
#=> 2
count_em [0, -2, -1, -2, 0]
#=> 0
count_em [0, -1, -2, 1, 0]
#=> 0
The last two examples reflect my understanding of the question.
The calculations are as follows.
arr = [0, 1, 2, 1, 0, -1, -2, -1, 0, 1, 2, 1, 0, -1, 0]
Use Enumerable#flat_map and Enumerator#with_index to insert a zero into a copy of arr after every zero but the first and last.
a = arr.flat_map.with_index { |e,i| (e.zero? && i > 0 && i < arr.size-1) ? [0,0] : [e] }
#=> [0, 1, 2, 1, 0, 0, -1, -2, -1, 0, 0, 1, 2, 1, 0, 0, -1, 0]
We can now use Enumerable#slice_when to obtain each sub-array that begins and end with a zero.
enum = a.slice_when { |*pair| pair == [0,0] }
#=> #<Enumerator: #<Enumerator::Generator:0x00007fa9fd970f28>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[0, 1, 2, 1, 0], [0, -1, -2, -1, 0], [0, 1, 2, 1, 0], [0, -1, 0]]
Lastly, use Enumerable#count to count the number of the four arrays generated by enum that are "downhill".
enum.count { |a| downhill?(a) }
#=> 2
This is because
downhill? [0, 1, 2, 1, 0] #=> false
downhill? [0, -1, -2, -1, 0] #=> true
downhill? [0, 1, 2, 1, 0] #=> false
downhill? [0, -1, 0] #=> true
To see how downhill? works, let
arr = [0, -1, -2, -1, 0]
Then
arr[1] > 0
#=> -1 > 0 => false, so do not return
imin = arr.each_index.min_by { |i| arr[i] }
#=> 2
decreasing?(arr[0..imin])
#=> decreasing? [0, -1, -2] => false, so do not return
decreasing?(arr[imin..-1].reverse)
#=> decreasing? [-2, -1, 0].reverse => [0, -1, -2]
# decreasing? [0, -1, -2] => true
Let's now look at the calculations performed by decreasing?. Suppose
arr = [0, -1, -2]
We see that
enum = arr.each_cons(2)
#=> #<Enumerator: [0, -1, -2]:each_cons(2)>
enum generates the following arrays:
enum.to_a
#=> [[0, -1], [-1, -2]]
Hence
arr.each_cons(2).all? { |e,f| f < e }
#=> true
because -1 < 0 #=> true and -2 < -1 #=> true.
I am applying Dijkstra on each node in python using spyder. I am getting the correct results too but I am unable to store the results (1-D arrays) into an nxn matrix by adding a row. I am storing the data obtained from Dijkstra in D_path variable but in variable explorer, it is giving me the type as NoneType Object. I am appending it with the row and appending the row with the matrix.
import sys
import numpy as np
class Graph():
def __init__(self, vertices):
self.V = vertices
self.graph = [[0 for column in range(vertices)]
for row in range(vertices)]
def printSolution(self, dist):
print("Vertex tDistance from Source")
for node in range(self.V):
print(node, "t", dist[node])
# A utility function to find the vertex with
# minimum distance value, from the set of vertices
# not yet included in shortest path tree
def minDistance(self, dist, sptSet):
# Initialize minimum distance for next node
min = sys.maxsize
# Search not nearest vertex not in the
# shortest path tree
for v in range(self.V):
if dist[v] < min and sptSet[v] == False:
min = dist[v]
min_index = v
return min_index
# Funtion that implements Dijkstra's single source
# shortest path algorithm for a graph represented
# using adjacency matrix representation
def dijkstra(self, src):
dist = [sys.maxsize] * self.V
dist[src] = 0
sptSet = [False] * self.V
for cout in range(self.V):
# Pick the minimum distance vertex from
# the set of vertices not yet processed.
# u is always equal to src in first iteration
u = self.minDistance(dist, sptSet)
# Put the minimum distance vertex in the
# shortest path tree
sptSet[u] = True
# Update dist value of the adjacent vertices
# of the picked vertex only if the current
# distance is greater than new distance and
# the vertex in not in the shortest path tree
for v in range(self.V):
if self.graph[u][v] > 0 and sptSet[v] == False and dist[v] > dist[u] + self.graph[u][v]:
dist[v] = dist[u] + self.graph[u][v]
self.printSolution(dist)
# Driver program
g = Graph(9)
g.graph = [[0, 4, 0, 0, 0, 0, 0, 8, 0],
[4, 0, 8, 0, 0, 0, 0, 11, 0],
[0, 8, 0, 7, 0, 4, 0, 0, 2],
[0, 0, 7, 0, 9, 14, 0, 0, 0],
[0, 0, 0, 9, 0, 10, 0, 0, 0],
[0, 0, 4, 14, 10, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 2, 0, 1, 6],
[8, 11, 0, 0, 0, 0, 1, 0, 7],
[0, 0, 2, 0, 0, 0, 6, 7, 0]
]
#D_path = list()
matrix=[] #define empty matrix
for i in range(9): #total row is 3
row=[]
D_path = g.dijkstra(i)
row.append(D_path) #adding 0 value for each column for this row
matrix.append(row) #add fully defined column into the row
print (matrix)
Note: This question poses a problem that I have already solved, however I feel my solution is very rudimentary and that other people, like myself, would benefit from a discussion with input from more experienced developers. Different approaches to solving the problem, as well as more sophisticated methods and algorithms would be really appreciated. I feel this is a good place to learn how Ruby can tackle what I consider to be a fairly difficult problem for a beginner.
Given a 6x6 2D Array arr:
1 1 1 0 0 0
0 1 0 0 0 0
1 1 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
We define an hourglass in arr to be a subset of values with indices falling in this pattern in arr's graphical representation:
a b c
d
e f g
There are 16 hourglasses in arr and an hourglass sum is the sum of an hourglass' values. Calculate the hourglass sum for every hourglass in arr, then print the maximum hourglass sum.
For example, given the 2D array:
arr = [
[-9, -9, -9, 1, 1, 1],
[ 0, -9, 0, 4, 3, 2],
[-9, -9, -9, 1, 2, 3],
[ 0, 0, 8, 6, 6, 0],
[ 0, 0, 0, -2, 0, 0],
[ 0, 0, 1, 2, 4, 0]
]
We calculate the following hourglass values:
-63, -34, -9, 12,
-10, 0, 28, 23,
-27, -11, -2, 10,
9, 17, 25, 18
Our highest hourglass value is from the hourglass:
0 4 3
1
8 6 6
My solution is:
def hourglass_sum(arr)
hourglasses = []
arr.each_with_index do |row, i|
# rescue clause to prevent iterating outside the array
unless arr[i].nil?
arr[i].length.times do |iteration|
# generate n 3x3 arrays
r1 = arr[i][iteration...iteration+3]
r2 = arr[i+1][iteration...iteration+3] if arr[i+1] != nil
r3 = arr[i+2][iteration...iteration+3] if arr[i+2] != nil
# rescue clause to stop creating 3x3 arrays that fall outside given input array
if arr[i+1] != nil && arr[i+2] != nil
# take all values except indices 0 and 5 from the 9 element array
result = r1 + [r2[1]] + r3
hourglasses << result.sum unless result.include? nil
end
end
end
end
p hourglasses.max
end
arr = [[-9, -9, -9, 1, 1, 1], [0, -9, 0, 4, 3, 2], [-9, -9, -9, 1, 2, 3], [0, 0, 8, 6, 6, 0], [0, 0 ,0, -2, 0, 0], [0, 0, 1, 2, 4, 0]]
hourglass_sum(arr)
# => 28
One option is to use Matrix methods.
require 'matrix'
ma = Matrix[*arr]
#=> Matrix[[-9, -9, -9, 1, 1, 1],
# [ 0, -9, 0, 4, 3, 2],
# [-9, -9, -9, 1, 2, 3],
# [ 0, 0, 8, 6, 6, 0],
# [ 0, 0, 0, -2, 0, 0],
# [ 0, 0, 1, 2, 4, 0]]
mi = Matrix.build(6-3+1) { |i,j| [i,j] }
#=> Matrix[[[0, 0], [0, 1], [0, 2], [0, 3]],
# [[1, 0], [1, 1], [1, 2], [1, 3]],
# [[2, 0], [2, 1], [2, 2], [2, 3]],
# [[3, 0], [3, 1], [3, 2], [3, 3]]]
def hourglass_val(r,c,ma)
mm = ma.minor(r,3,c,3)
mm.sum - mm[1,0] - mm[1,2]
end
max_hg = mi.max_by { |r,c| hourglass_val(r,c,ma) }
#=> [1,2]
hourglass_val(*max_hg,ma)
#=> 28
[1,2] are the row and column indices of the top-left corner of an optimal hourglass in arr.
Here is an option I came up with.
def width_height(matrix)
[matrix.map(&:size).max || 0, matrix.size]
end
def sum_with_weight_matrix(number_matrix, weight_matrix)
number_width, number_height = width_height(number_matrix)
weight_width, weight_height = width_height(weight_matrix)
width_diff = number_width - weight_width
height_diff = number_height - weight_height
0.upto(height_diff).map do |y|
0.upto(width_diff).map do |x|
weight_height.times.sum do |ry|
weight_width.times.sum do |rx|
weight = weight_matrix.dig(ry, rx) || 0
number = number_matrix.dig(y + ry, x + rx) || 0
number * weight
end
end
end
end
end
arr = [
[-9, -9, -9, 1, 1, 1],
[ 0, -9, 0, 4, 3, 2],
[-9, -9, -9, 1, 2, 3],
[ 0, 0, 8, 6, 6, 0],
[ 0, 0, 0, -2, 0, 0],
[ 0, 0, 1, 2, 4, 0],
]
weights = [
[1, 1, 1],
[0, 1, 0],
[1, 1, 1],
]
sum_matrix = sum_with_weight_matrix(arr, weights)
#=> [
# [-63, -34, -9, 12],
# [-10, 0, 28, 23],
# [-27, -11, -2, 10],
# [ 9, 17, 25, 18]
# ]
max_sum = sum_matrix.flatten.max
#=> 28
This solution uses the width_diff and height_diff to create an output matrix (4x4 for the sample data 0.upto(6 - 3).to_a #=> [0, 1, 2, 3]). The indexes of the weight_matrix (rxand ry) will be used as relative index compared to the larger number_matrix.
If your 2d array always has the same number of elements for each sub-array you can replace matrix.map(&:size).max with matrix[0]&.size || 0 to speed up determining the matrix width. The current solution uses the maximum size of the sub-arrays. Sub-arrays having a smaller size will use 0 for the missing elements thus not effecting the sum.
My solution might be a bit variable heavy. I've done this to have descriptive variable names, that hopefully tell you most you need to know about the solution. You can shorten variable names, or remove them completely when you feel like you don't need them.
If something isn't clear just ask away in the comments.
Without using the Matrix class, here's how I've done it for any arbitrary rectangular array:
offsets = [[-1, -1], [-1, 0], [-1, 1], [0, 0], [1, -1], [1, 0], [1, 1]]
sums = 1.upto(arr.length - 2).flat_map do |i|
1.upto(arr[0].length - 2).map do |j|
offsets.map {|(x, y)| arr[i+x][j+y] }.sum
end
end
puts sums.max
The values we're interested in are just offsets from a current position. We can map out the values in the array relative to the current position by some row and column offset, sum them, then select the max of the sums.
This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 4 years ago.
a = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
b = []
row = []
3.times { row << 0 }
3.times { b << row }
p a #=> [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
p b #=> [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
p a == b #=> true
p (a[1][1] = 1) == (b[1][1] = 1) #=> true
# and yet ...
a[1][1] = 1
b[1][1] = 1
p a #=> [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
p b #=> [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
With this code, I'd expect the modified b to be the same as the modified a, but instead, the second element of each nested array is modified.
What am I missing? Why do a[1][1] = 1 and b[1][1] = 1 produce different results?
The line 3.times { b << row } adds 3 references to the same array to b, so modifying it once changes it three times in the array.
Try updating this to dup the row as you push it into b, i.e. 3.times { b << row.dup }.
(As I'm typing I'm seeing #mudasobwa beat me to it in a comment :) )
This approach will ensure b is filled with three unique arrays, rather than the same one three times.
If you're unsure, you can check the object_id, i.e. b.map(&:object_id) - at present, these will all be the same.
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.
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]]
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.