I need to create a 2D grid of 0's and 1's representing an image, and create a blur method which would change any 0 next to a 1 (left, right, above or below) into a 1, like so:
0000 => 0000
0000 0010
0010 0111
0000 0010
Next I have to allow the user to pass in a number that would allow the blur to extend multiple spaces in each direction. If I call image.blur(2)
, it would extend 2 spaces in each direction, but each first step would have to call blur again to account for diagonals. For example:
00000000 => 00010000
00000000 00111000
00010000 01111100
00000000 00111000
00000000 00010000
Here is my code.
class Image
attr_accessor :picture
def initialize(*rows)
@picture = *rows
end
def output_image
@picture.each_index do |i|
puts @picture[i].join
end
end
def blur(distance=1)
@blurred_image = Array.new(@picture.length, 0) {Array.new(@picture[0].length, 0)} #create new array of zeroes the size of @picture
@picture.each_index do |i|
@picture[i].each_index do |j|
if @picture[i][j] == 1
@blurred_image[i][j] = 1 if @blurred_image[i][j]
@blurred_image[i][j-1] = 1 if @blurred_image[i][j-1]
@blurred_image[i][j+1] = 1 if @blurred_image[i][j+1]
@blurred_image[i-1][j] = 1 if @blurred_image[i-1][j]
@blurred_image[i+1][j] = 1 if @blurred_image[i+1][j]
end
end
end
if distance > 1
@picture = @blurred_image #save progress of image blur in @picture so we can continue in recursive call
blur(distance-1) #iterate as long as distance > 1
elsif distance == 1 #necessary so blurred image isn't printed 'distance' times
@blurred_image.each_index do |i|
puts @blurred_image[i].join
end
end
end
end
pic = Image.new(
[0,0,0,0,0,0,0,0,0],
[1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,1,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
)
pic.blur(3)
My function works, but only if the 1's don't extend past the bounds of the array. If I put a 1 in a corner, it looks like my function attempts to edit the value of an index that doesn't exist, and I get the following message:
image_blur.rb:28:in `block (2 levels) in blur': undefined method `[]' for nil:NilClass (NoMethodError)
from image_blur.rb:22:in `each_index'
from image_blur.rb:22:in `block in blur'
from image_blur.rb:21:in `each_index'
from image_blur.rb:21:in `blur'
from image_blur.rb:35:in `blur'
from image_blur.rb:35:in `blur'
from image_blur.rb:47:in `<main>'
I'm trying to tell it to only assign a 1 to an index if the index exists. I'd appreciate any help.
Looks like you're trying to check two levels deep before you check one level deep.
eg it's possible that picture[1]
doesn't exist... so if you then try to check picture[i][j]
it fails.
You can try checking if each level is present before indexing into it...
if @picture[i] && @picture[i][j] && @picture[i][j] == 1
if @blurred_image[i].present?
@blurred_image[i][j] = 1 if @blurred_image[i][j]
@blurred_image[i][j-1] = 1 if @blurred_image[i][j-1]
@blurred_image[i][j+1] = 1 if @blurred_image[i][j+1]
end
@blurred_image[i-1][j] = 1 if @blurred_image[i-1] && @blurred_image[i-1][j]
@blurred_image[i+1][j] = 1 if @blurred_image[i+1] && @blurred_image[i+1][j]
My first thought would be to drop a lookup based solution like this in:
adjacents = [[-1, 0], [0, -1], [0, 0], [1, 0], [0, 1]].freeze
@blurred_image = Array.new(@picture.length, 0) { Array.new(@picture[0].length, 0) }
@picture.each_index do |i|
@picture[i].each_index do |j|
if @picture[i][j] == 1
adjacents.each { |x, y| (a = @blurred_image[i+x]) && a[j+y] &&= 1 }
end
end
end
Note the &&=
operator will perform the assignment only if the left hand side is truthy. So it would fail, for instance, if you initialised your inner arrays with nil
values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With