Changing surrounding elements of an array element - arrays

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.

Related

Is there a way to find the UNIQUE row indices of maximum columnar values in a 2D NumPy array?

For each column in a 2D NumPy array, the column's maximum value can appear more than once. I would like to find the row index for each column maximum, without repeating row indices.
Here is an example that demonstrates why np.argmax doesn't work:
import numpy as np
a = np.array([[1, 1, 0],
[1, 0, 1],
[0, 0, 1]])
ind = np.argmax(a, axis=0)
print(ind)
Output:
[0 0 2]
I want the result: [1, 0, 2] for this example.
That is:
The row index for the second column must be 0
This implies that the row index for the first column must be 1
This in turn implies that the row index for the third column must be 2
A slightly more complex example is this array:
a = np.array([[1, 1, 0],
[1, 1, 1],
[0, 0, 1]])
In this case, there is no column with a unique maximum value. I'd be happy with either of these answers:
[0, 1, 2]
[1, 0, 2]
An even more complex example is:
a = np.array([[1, 1, 1],
[1, 1, 1],
[0, 1, 1]])
In this case, I'd be happy with any of these answers:
[0, 1, 2]
[0, 2, 1]
[1, 0, 2]
[1, 2, 0]
I can solve these problems with loops and logical conditions, but I'm wondering if there is a way to solve the problem using numpy functions?
May be overkill, but you can use scipy.optimize.linear_sum_assignment:
from scipy.optimize import linear_sum_assignment
a = np.array([[1, 1, 0],
[1, 0, 1],
[0, 0, 1]])
linear_sum_assignment(-a.T)[1]
# array([1, 0, 2])
Note that you can always reduce to the 0,1 case using
something like
abin = a==a.max(axis=0)
This can speed up the assignment quite a bit.
Alternatively, see this post for a graph theory solution.
Inspired by the solution suggested here:
import numpy_indexed as npi
ind = np.argwhere(a == a.max(0))
l = np.array(npi.group_by(ind[:,1]).split(ind[:, 0]))
def pick_one(a, index, buffer, visited):
if index == len(a):
return True
for item in a[index]:
if item not in visited:
buffer.append(item)
visited.add(item)
if pick_one(a, index + 1, buffer, visited):
return True
buffer.pop()
visited.remove(item)
return False
buffer = []
pick_one(l, 0, buffer, set())
print(buffer)
example:
a = np.array([[1, 1, 0],
[1, 0, 1],
[0, 0, 1]])
output:
[1, 0, 2]

Trying to change single value in array created with iteration changes multiple values. Why? [duplicate]

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.

How to split a Ruby array into subarrays of unequal size using a delimiter

I have the following array:
arr = [0, 1, 1, 2, 3, 1, 0, 0, 1]
Without changing the order of the values, I need to subdivide arr into smaller arrays at every occurrence of 0, such that the result would be:
arr = [ [0, 1, 1, 2, 3, 1], [0], [0, 1] ]
If arr were a string, I could use .split("0") and then prepend the delimiter to each subarray. What would be the most efficient equivalent to .split() in plain Ruby for arrays?
Enumerable#slice_before does this exact thing:
arr = [0, 1, 1, 2, 3, 1, 0, 0, 1]
p arr.slice_before(0).to_a
# => [[0, 1, 1, 2, 3, 1], [0], [0, 1]]
See it on repl.it: https://repl.it/FBhg
Since ActiveSupport defines an Array#split method in Ruby, we can use it as a starting point:
class Array
def split(value = nil)
arr = dup
result = []
if block_given?
while (idx = arr.index { |i| yield i })
result << arr.shift(idx)
arr.shift
end
else
while (idx = arr.index(value))
result << arr.shift(idx)
arr.shift
end
end
result << arr
end
end
# then, using the above to achieve your goal:
arr = [0, 1, 1, 2, 3, 1, 0, 0, 1]
arr.split(0).map { |sub| sub.unshift(0) }
# => [[0], [0, 1, 1, 2, 3, 1], [0], [0, 1]]
Note that your verbal phrasing of the algorithm (split and prepend) is what is happening here, but your expected output is different (there's an additional zero because of the way split works).
Do you want to split before each zero? For this you could use slice_before.
Do you want to split but drop empty arrays? This could be done with a quick compact before prepending, but you would lose the [0] subarray.
Do you want to split but drop the first element if empty?
Do you want to split on /0+/?

2 Dimensional Array Transformation

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.

Returning indexes for all subarray in 2D array that contains n

I am trying to find a way to return the indexes of ALL subarrays that contain the number 1. For example, in this 2D array:
ary= [[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]]
My goal is to return [1, 2].
Using the following:
ary.index(ary.detect{|xyz| xyz.include?(1)})
returns only 1, which is the first subarray with 1 detected.
How can I get all the indexes of the subarray that contains 1?
ary.map.with_index {|sub_ary, i| sub_ary.include?(1) ? i : nil}.compact
=> [1, 2]
Use select
irb(main):013:0> ary.select {|xyz| xyz.include?(1)}.map {|i| ary.index i}
=> [1, 2]
ary.each_index.select { |i| ary[i].include?(1) }
#=> [1,2]

Resources