convert adjacency matrix to adjacency list to represent a graph - ruby

I am trying to write a code in Ruby to convert a given graph's adjacency matrix to adjacency list. When I verified my solution for the given input mentioned here my output was slightly different. I need some direction in understanding where exactly is the issue. Below is my code:
def convert_adj_matrix_to_list(arr)
list = {}
start = 0
last = 0
arr.each_index do |row|
# row = [0, 0, 1]
puts "row = #{arr[row]}"
for col in 0..arr[row].size-1
puts "row = #{row}, col = #{col}"
puts "arr[#{row}][#{col}] = #{arr[row][col]}"
list[row] = col if arr[row][col] == 1
end
puts
end
list
end
arr = [ [0, 0, 1], [0, 0, 1], [1, 1, 0] ]
puts convert_adj_matrix_to_list(arr)
Output:
row = [0, 0, 1]
row = 0, col = 0
arr[0][0] = 0
row = 0, col = 1
arr[0][1] = 0
row = 0, col = 2
arr[0][2] = 1
row = [0, 0, 1]
row = 1, col = 0
arr[1][0] = 0
row = 1, col = 1
arr[1][1] = 0
row = 1, col = 2
arr[1][2] = 1
row = [1, 1, 0]
row = 2, col = 0
arr[2][0] = 1
row = 2, col = 1
arr[2][1] = 1
row = 2, col = 2
arr[2][2] = 0
{0=>2, 1=>2, 2=>1}

Printing inside a function like this is a side effect and should only be done for debugging. Better to print in the caller.
Logically, your code is only keeping track of the last link seen in the matrix and returns a hash that maps ints to ints rather than a hash that maps ints to int arrays.
You can get the hash by mapping each row, then filtering the row's indexes based on whether the cell has the value 1. Once you've built a 2d array of pairs with the first item in a pair being the source node integer and the second item in a pair being the array of its edges to other nodes, call .to_h on it to produce a hash.
def adj_matrix_to_list(matrix)
matrix.each_with_index.map do |row, i|
[i, row.each_index.select {|j| row[j] == 1}]
end.to_h
end
matrix = [[0, 0, 1],
[0, 0, 1],
[1, 1, 0]]
p adj_matrix_to_list(matrix) # {0=>[2], 1=>[2], 2=>[0, 1]}
Having said that, using a hash with keys 0, 1, 2 ... n is an antipattern, since arrays are a faster and more natural way to represent a sequentially-indexed list of things. I'd do:
def adj_matrix_to_list(matrix)
matrix.map do |row|
row.each_index.select {|i| row[i] == 1}
end
end
matrix = [[0, 0, 1],
[0, 0, 1],
[1, 1, 0]]
p adj_matrix_to_list(matrix) # [[2], [2], [0, 1]]
and usage is the same as the hash version.

Related

Algorithm for array with `while` or `until` loop

I have:
array = [1, 4, -1, 3, 2]
I want a new array that follows the following logic:
First element is located at index 0, so it is 1.
Second element is located at index 1 (because value for index 0 was 1).
Third element is located at index 4, so it is 2.
And so on until the loop meets value -1, which is the last value, and it should brake.
The new array should be:
[1, 4, 2, -1]
I have:
def task(a)
array = []
a.each_with_index do |v, i|
result = a[i]
until a[i] == -1
array << a[result]
end
end
puts result
end
As others say, you need to change the index in your loop. Also, if you want -1 in the result, you should exit at bottom. And with_index will give you indices in order, which is not what you want here. This will do what you want:
def task(a)
i = 0
array = []
begin
i = a[i]
array << i
end until i == -1
array
end
p task([1, 4, -1, 3, 2])
# => [1, 4, 2, -1]
until a[i] == -1
array << a[result]
end
This code is looping eternally - there is nothing to change i .
As discussed in the comments, you are looping through the array which is not what you require.
You could use a recursive method to handle jumping from one element to another based on previous value. Consider the following:
arr = [1, 4, -1, 3, 2]
def task(arr, n=0, result=[])
if arr[n] == -1
return result + [-1]
end
r = arr[n]
task(arr, r, result + [r])
end
puts task(arr)
input_array = [1, 4, -1, 3, 2]
last_valid_index = input_array.find_index { |entry| entry < 0 }
first_element = input_array.first
last_element = input_array[last_valid_index]
middle_elements = (1..last_valid_index).map { |i| input_array[input_array[i-1]]}
output_array = [first_element] + middle_elements + [last_element]
p output_array
# => [1, 4, 2, -1]
you could to most of it on one line like so, but I think the more verbose version is more self documenting.
input_array = [1, 4, -1, 3, 2]
last_valid_index = input_array.find_index { |entry| entry < 0 }
output_array = [input_array.first] + (1..last_valid_index).map { |i| input_array[input_array[i-1]]} + [input_array[last_valid_index]]
p output_array
# => [1, 4, 2, -1]
I'd suggest this option, just to avoid infinite loops or index out range:
i, ary = 0, [array[0]]
array.size.times do
break if array[i] == -1 or array[i] > array.size - 1
i = array[i]
ary << array[i]
end
ary #=> [1, 4, 2, -1]
An infinite loop happens for example when array = [1, 4, -1, 0, 3].
Index out of range can happen when array = [1, 4, 6, 3, 2]

ruby enumerators: immediately skip multiple iterations (or start iterating from n)

I'm iterating over permutations of a list (18 items) like this:
List = [item0..item18] # (unpredictable)
Permutation_size = 7
Start_at = 200_000_000
for item, i in List.repeated_permutation(Permutation_size).each_with_index
next if i < Start_at
# do stuff
end
Start_at is used to resume from a previously saved state so it's always different but it takes almost 200s to reach 200 million so I'm wondering if there is a faster way to skip multiple iterations or start at iteration n (converting the enumerator to an array takes even longer). If not, a way to create a custom repeated_permutation(n).each_with_index (that yields results in the same order) would also be appreciated.
Feel free to redirect me to an existing answer (I haven't found any)
PS. (what I had come up with)
class Array
def rep_per_with_index len, start_at = 0
b = size
raise 'btl' if b > 36
counter = [0]*len
# counter = (start_at.to_s b).split('').map {|i| '0123456789'.include?(i) ? i.to_i : (i.ord - 87)} #this is weird, your way is way faster
start_at.to_s(b).chars.map {|i| i.to_i b}
counter.unshift *[0]*(len - counter.length)
counter.reverse!
i = start_at
Enumerator.new do |y|
loop do
y << [counter.reverse.map {|i| self[i]}, i]
i += 1
counter[0] += 1
counter.each_with_index do |v, i|
if v >= b
if i == len - 1
raise StopIteration
else
counter[i] = 0
counter[i + 1] += 1
end
else
break
end
end
end
end
end
end
I first construct a helper method, change_base, with three arguments:
off, the base-10 offset into the sequence of repeated permutations of the given array arr,
m, a number system base; and
p, the permutation size.
The method performs three steps to construct an array off_m:
converts off to base m (radix m);
separates the digits of the base m value into an array; and
if necessary, pads the array with leading 0s to make it of size p.
By setting m = arr.size, each digit of off_m is an offset into arr, so off_m maps the base-10 offset to a unique permutation of size p.
def change_base(m, p, off)
arr = off.to_s(m).chars.map { |c| c.to_i(m) }
arr.unshift(*[0]*(p-arr.size))
end
Some examples:
change_base(16, 2, 32)
#=> [2, 0]
change_base(16, 3, 255)
#=> [0, 15, 15]
change_base(36, 4, 859243)
#=> [18, 14, 35, 31]
18*36**3 + 14*36**2 + 35*36**1 + 31
#=> 859243
This implementation of change_base requires that m <= 36. I assume that will be sufficient, but algorithms are available to convert base-10 numbers to numbers with arbitrarily-large bases.
We now construct a method which accepts the given array, arr, the size of each permutation, p and a given base-10 offset into the sequence of permutations. The method returns a permutation, namely, an array of size p whose elements are elements of arr.
def offset_to_perm(arr, p, off)
arr.values_at(*change_base(arr.size, p, off))
end
We can now try this with an example.
arr = (0..3).to_a
p = 2
(arr.size**p).times do |off|
print "perm for off = "
print " " if off < 10
print "#{off}: "
p offset_to_perm(arr, p, off)
end
perm for off = 0: [0, 0]
perm for off = 1: [0, 1]
perm for off = 2: [0, 2]
perm for off = 3: [0, 3]
perm for off = 4: [0, 1]
perm for off = 5: [1, 1]
perm for off = 6: [2, 1]
perm for off = 7: [3, 1]
perm for off = 8: [0, 2]
perm for off = 9: [1, 2]
perm for off = 10: [2, 2]
perm for off = 11: [3, 2]
perm for off = 12: [0, 3]
perm for off = 13: [1, 3]
perm for off = 14: [2, 3]
perm for off = 15: [3, 3]
If we wish to begin at, say, offset 5, we can write:
i = 5
p offset_to_perm(arr, p, i)
[1, 1]
i = i.next #=> 6
p offset_to_perm(arr, p, i)
[2, 1]
...

iterate with a method within ruby class

Class Image initializes with an array of 0's and 1's. I have method transform, such that
[[0,0,0],
[0,1,0],
[0,0,0]]
returns
[[0,1,0],
[1,1,1],
[0,1,0]]
I want to implement method blur(n), which iterates n times with transform, such calling blur(2) with
[[0,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]]
returns
[[0,0,0,0,1,0,0,0,0],
[0,0,0,1,1,1,0,0,0],
[0,0,1,1,1,1,1,0,0],
[0,0,0,1,1,1,0,0,0],
[0,0,0,0,1,0,0,0,0]]
I'm trying to use transform iteratively to achieve this, but I'm getting undefined method 'map' for #<Context::Image:0x000000012eb020> when calling blur with an instance of Image. How can I iterate over each successive transformation, such that blur returns the latest version with the maximum n transformations?
class Image
attr_accessor :array
def initialize(array)
self.array = array
end
def output_image
self.array.each do |item|
puts item.join
end
end
def transform #changes adjacent a 1's adjacent 0's into 1
cloned = self.array.map(&:clone)
#scan original array for 1; map crosses into clone if found
self.array.each.with_index do |row, row_index|
row.each.with_index do |cell, col|
if cell == 1
cloned[row_index][col+1] = 1 unless col+1 >= row.length #copy right
cloned[row_index+1][col] = 1 unless row_index+1 >= cloned.length # copy down
cloned[row_index][col-1] = 1 unless col.zero? # copy left
cloned[row_index-1][col] = 1 unless row_index.zero? #copy up
end
end
end
cloned
end
def blur(n) #should call transform iteratively n times
blurred = Image.new(self)
n.times do
blurred = blurred.transform
end
blurred
end
end
You could use the Matrix class.
require 'matrix'
class Matrix
def el(r,c)
if r < 0 || r >= row_count || c < 0 || c >= column_count
0
else
self[r,c]
end
end
def transform
Matrix.build(row_count, column_count) { |r,c|
[el(r,c), el(r-1,c), el(r+1,c), el(r,c-1), el(r,c+1)].max }
end
end
Given a row-column pair, r, c, the helper method el returns 0 if the row or column is outside the bounds of the matrix and the value at [r,c] otherwise.
nrows = 5
ncols = 5
m = Matrix.build(nrows, ncols) { |r,c| (r==nrows/2 && c==ncols/2) ? 1 : 0 }
#=> Matrix[[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]]
m = m.transform
#=> Matrix[[0, 0, 0, 0, 0],
# [0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0],
# [0, 0, 0, 0, 0]]
m = m.transform
# Matrix[[0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [1, 1, 1, 1, 1],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0]]
m.to_a
#=> [[0, 0, 1, 0, 0],
# [0, 1, 1, 1, 0],
# [1, 1, 1, 1, 1],
# [0, 1, 1, 1, 0],
# [0, 0, 1, 0, 0]]
map is a method available to an Array, but not to your custom class Image.
I suggest calling map on your instance variable #array instead. Then, when your transforms are completed, create a new Image instance with that transformed array.
Below is an example of code that should work. Note that transform and blur take input arrays as parameters, so they do not rely on any instance state. Therefore, I've made them class methods instead of instance methods. This allows your users to use them without having to create an instance, if all they want to do is the array transformation. It also makes those methods easy to extract to a module in future refactorings. I've added an instance method, blurred_image, which applies the transformation to the instance and returns a new Image instance.
def self.transform(input_array) #changes adjacent a 1's adjacent 0's into 1
cloned = input_array.map(&:clone)
#scan original array for 1; map crosses into clone if found
input_array.each.with_index do |row, row_index|
row.each.with_index do |cell, col|
if cell == 1
cloned[row_index][col+1] = 1 unless col+1 >= row.length #copy right
cloned[row_index+1][col] = 1 unless row_index+1 >= cloned.length # copy down
cloned[row_index][col-1] = 1 unless col.zero? # copy left
cloned[row_index-1][col] = 1 unless row_index.zero? #copy up
end
end
end
cloned
end
def self.blur(input_array, transform_count) #should call transform iteratively n times
blurred = input_array
transform_count.times { blurred = transform(blurred) }
Image.new(blurred)
end
def blurred_image(transform_count)
self.class.new(self.class.blur(array, transform_count))
end

Is there a ruby idiom for popping items from an array while a condition is true

Is there a Ruby idiom for popping items from an array while a condition is true, and returning the collection?
I.e,
# Would pop all negative numbers from the end of 'array' and place them into 'result'.
result = array.pop {|i| i < 0}
From what I can tell, something like the above doesn't exist.
I'm currently using
result = []
while array.last < 0 do
result << array.pop
end
Maybe you are looking for take_while?
array = [-1, -2, 0, 34, 42, -8, -4]
result = array.reverse.take_while { |x| x < 0 }
result would be [-8, -4].
To get the original result back you could use drop_while instead.
result = array.reverse.drop_while { |x| x < 0 }.reverse
result would be [-1, -2, 0, 34, 42] in this case.
You could write it yourself:
class Array
def pop_while(&block)
result = []
while not self.empty? and yield(self.last)
result << self.pop
end
return result
end
end
result = array.pop_while { |i| i < 0 }
In case your looking for a solution to pop all items that satisfy a condition, consider a select followed by a delete_if, e.g.
x = [*-10..10].sample(10)
# [-9, -2, -8, 0, 7, 9, -1, 10, -10, 3]
neg = x.select {|i| i < 0}
# [-9, -2, -8, -1, -10]
pos = x.delete_if {|i| i < 0}
# [0, 7, 9, 10, 3]
# note that `delete_if` modifies x
# so at this point `pos == x`

Iterate array using range

This is what I want to do
a = [1, 2, 3, 4]
a[1] = 0
a[2] = 0
one way to do this is to loop
(1..2).each { |x| x = 0 }
is there a way to do this somehow with ranges or splats? something like
a[(1..2)] = 0
a = [1, 2, 3, 4]
a[1..2] = [0] * 2
p a #[1, 0, 0, 4]
You can't just type a[1..2] = 0 at line 2, cause the array a will become [1, 0, 4]
Or, with Array#fill
a.fill(0, 1..2)
With range
ary = [1, 2, 3, 4]
ary[1..2] = [0,0]
Using [start, length]
a = [1,2,3,4]
a[1,2] = [0,0]

Resources