Ruby - protect a variable while iterating through a loop - ruby

I'm trying to perform an action with a nested array through a loop. The loop executes once but then I get a nomethod error because the variable is not reset.
array = [[9, 2, 0, 0], [4, 1, 2, 2], [7, 1, 5, 5], [6, 1, 3, 1]]
comments = [[0, 0, 0], [1, 1, 1], [2, 2, 2]]
def shift_comments(array)
array.each {|x| x.shift}
end
def map_distance_coordinants(array)
array2 = array.map {|x,y| [Math.sqrt(x*x + y*y)]}
array2
end
def input_is_comment_format(array, comments)
distance_coordinants = shift_comments(comments)
mapped_coordinanats = map_distance_coordinants(distance_coordinants)
print mapped_coordinanats
print comments
end
i = 0
while i < array.length
input_is_comment_format(array[i], comments)
i += 1
end
Returns:
[[0.0], [1.4142135623730951], [2.8284271247461903]][[0, 0], [1, 1], [2, 2]]
temp4.rb:9:in `block in map_distance_coordinants': undefined method `*' for nil:NilClass (NoMethodError)
How do I protect 'comments' so that I can use it for each iteration of the loop? Thanks.

You could use dup:
input_is_comment_format(array[i], comments.dup)
so you have a copy of the array to work with and your original array won't be modified.

Related

Ruby program to create even_odd method that accepts the whole number

I need help to write even_odd method that accepts an array of whole number.
It should return an array of 2 arrays
The first nested array should contain only the odd numbers
The second nested array should contain only the even numbers
If there are no even or odd numbers, the respective inner array should be empty
Output should look like this : -
even_odd([3, 5, 8, 2, 4, 6])
[[3, 5], [2, 4, 6, 8]]
even_odd([3, 5])
[[3, 5], []]
even_odd([2, 4])
[[], [2, 4]]
I am new to ruby programming, I have tried below but not getting the result :-
def even_odd(numbers)
arr1, arr2 = []
idx = 0
while idx < numbers.length
if numbers[idx] % 2 == 0
puts arr1[idx]
elsif
puts arr2[idx]
end
idx += 1
end
end
puts even_odd([2, 3, 6])
Error :-
main.rb:6:in `even_odd': undefined method `[]' for nil:NilClass (NoMethodError)
from main.rb:13:in `<main>'
I would do this
def even_odd(numbers)
numbers.sort.partition(&:odd?)
end
even_odd([3, 5, 8, 2, 4, 6])
# => [[3, 5], [2, 4, 6, 8]]
even_odd([3, 5])
# => [[3, 5], []]
even_odd([2, 4])
# => [[], [2, 4]]
puts is a print statement in ruby, not an append one. It also doesn't run a function/method. You'll also want to call the index on the numbers array inside the if...else block.
This should do the trick:
def even_odd(numbers)
arr1, arr2 = [], []
idx = 0
while idx < numbers.length
if numbers[idx] % 2 == 0
arr1 << numbers[idx]
elsif
arr2 << numbers[idx]
end
idx += 1
end
return arr1, arr2
end
arrays = even_odd([2, 3, 6])
puts arrays

Why does changing a duped object alter the original? [duplicate]

temp gets #board.dup, and #board array is modified. However, temp gets modified as well! I have tried reading all the related documentations but still couldn't figure out an explanation.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.dup #Also tried .clone
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
Output:
temp: [[1, 2], [3, 4], [5, 6]]
#board: [[1, 2], [3, 4], [5, 6]]
temp: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]] # <= Why did it change?
#board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
What can I do to ensure temp doesn't get modified?
You have Array with Arrays, so you dup the first array but, inside object point to the same instance. In this case you just modify the same source.
like here:
arr = [[1, 2, 3]]
arr2 = arr.dup
arr2[0] << 1
p arr
# => [[1, 2, 3, 1]]
p arr2
# => [[1, 2, 3, 1]]
So you must use dup for all array instance like this.
arr = [[1, 2, 3]]
arr3 = arr.map(&:dup)
arr3[0] << 1
p arr
# => [[1, 2, 3]]
p arr3
# => [[1, 2, 3, 1]]
In your case use this map.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.map(&:dup) # dup all
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
#
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
The reason is clone and dup produce a shallow copy. Thus the object ids of the inner arrays are still the same: those are the same arrays.
What you need is a deep clone. There is no built-in method in the standard library able to do that.
From the docs:
dup produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
That said: You will need to make a deep-copy of your array.
When you are using the ActiveSupport gem (Rails) you can use its deep_dup method instead of just calling dup:
temp = #board.deep_dup
Without gems mashaling might be a easy solution to deep dup almost everything, without knowing about the internals of an object:
temp = Marshal.load(Marshal.dump(#board))
You can add a deep_dup method for nested arrays:
class Array
def deep_dup
map {|x| x.deep_dup}
end
end
# To handle the exception when deepest array contains numeric value
class Numeric
def deep_dup
self
end
end
class Test
def initialize
#board = [[1,2], [3,4], [5,6]]
end
def modify
temp = #board.deep_dup
...
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

Transposing rows to columns (with limitations on what methods I can use)

I'm doing a practice problem and the question is to transpose rows to columns in Ruby.
I understand while loops are "rookie-esque" in Ruby, but I think they prefer I use basic methods and control flow statements: while, each, map. Obviously, I can't use Array#transpose. They want me to basically write Array#transpose from scratch.
It keeps telling me:
undefined method `[]=' for nil:NilClass (NoMethodError)
Here is my code:
def transpose(rows)
idx1 = 0
cols = []
while idx1 < rows.count
idx2 = 0
while idx2 < rows[idx1].count
cols[idx1][idx2] = rows[idx2][idx1]
idx2 += 1
end
idx1 += 1
end
return cols
end
puts transpose(rows = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
])
Perhaps this will help you:
def transpose(rows)
idx1 = 0 # => 0
cols = [] # => []
while idx1 < rows.count # => true
idx2 = 0 # => 0
while idx2 < rows[idx1].count # => true
cols[idx1][idx2] = rows[idx2][idx1] # ~> NoMethodError: undefined method `[]=' for nil:NilClass
idx2 += 1
end
idx1 += 1
end
return cols
end
puts transpose(rows = [
[0, 1, 2], # => [0, 1, 2]
[3, 4, 5], # => [3, 4, 5]
[6, 7, 8] # => [6, 7, 8]
])
# ~> NoMethodError
# ~> undefined method `[]=' for nil:NilClass
That was created using "Seeing Is Believing" in Sublime Text 2.
Breaking it down, here's where you're going wrong:
cols = [] # => []
cols[0] # => nil
cols[0][0] # ~> NoMethodError: undefined method `[]' for nil:NilClass
You can't use a sub-index on a nil. The first index doesn't exist in the empty array.
In your outer while loop you need to make cols[idx1] an empty array, else it is nil in your inner while loop:
while idx1 < rows.count
cols[idx1] = []
idx2 = 0
while idx2 < rows[idx1].count
# ...
end
end
You could do that using Array#new with a block:
Array.new(rows.size) { |i| Array.new(rows.size) { |j| rows[j][i] } }
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
Alternatively, you could use methods from the class Matrix, for which you need:
require 'matrix'
Here's an easy way that does not use Array#transpose, though it may not satisfy the spirit of the question:
Matrix[*rows].transpose.to_a
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
However, you could do this:
Matrix.build(rows.size) { |i,j| rows[j][i] }.to_a
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
Another way to do this for comparison using nested Enumerable#maps:
rows = [[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]
def my_transpose(matrix)
matrix.first.size.times.map { |column| matrix.map { |row| row[column] } }
end
my_transpose(rows)
# => [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
my_transpose(my_transpose(rows))
# => [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

Swapping two numbers with while loop in Ruby

I'd like to get [[2, 1, 3], [1, 3, 2]] from [1, 2, 3] in Ruby.
For [1, 2, 3, 4], I'd like to get [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
Rule: Within two numbers, if left one is smaller then it swap the position.
I have the following codes so far but it returns [[2, 3, 1], [2, 3, 1]]
What am I doing wrong here? I appreciate any inputs.
In amidakuji.rb
class Amidakuji
def initialize(column, rung)
#column = column
#rung = rung
#myarr = []
#per_arr = []
#build_arr = []
end
def build_initial
#arr = (1..#column).to_a
end
def swap_element
i = 0
arr = build_initial
while i < #column - 1 do
#build_arr << swap(arr, i)
i += 1
end
#build_arr
end
def swap(arr, a)
if arr[a] < arr[a + 1]
arr[a], arr[a + 1] = arr[a + 1], arr[a]
end
arr
end
end
In amidakuji_spec.rb
it 'should create an array with swapped elements' do
expect(#kuji1.swap_element).to eq ([[2, 1, 3], [1, 3, 2]])
end
Results
Failures:
expected: [[2, 1, 3], [1, 3, 2]]
got: [[2, 3, 1], [2, 3, 1]]
You can do this quite compactly by using the methods Enumerable#each_cons and Enumerable#map.
Code
def doit(arr)
(0...arr.size).each_cons(2).map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
end
Examples
doit([1,2,3]) #=> [[2, 1, 3], [1, 3, 2]]
doit([1,2,3,4]) #=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
doit([1,2,3,4,5]) #=> [[2, 1, 3, 4, 5], [1, 3, 2, 4, 5],
#=> [1, 2, 4, 3, 5], [1, 2, 3, 5, 4]]
Explanation
arr = [1,2,3,4]
b = (0...arr.size).each_cons(2)
#=> #<Enumerator: 0...4:each_cons(2)>
To view the contents of this enumerator:
b.to_a
#=> [[0, 1], [1, 2], [2, 3]]
Lastly
b.map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
#=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
In the last step, consider the first element of b that is passed to map, which assigns the following values to the block variables:
i => 0
j => 1
We then make a copy of arr, swap the elements offsets 0 and 1, making
a => [2, 1, 3, 4]
and then enter a at the end of the block, causing map to replace [0, 1] with that array.
Given what you're trying to accomplish and the output you're getting, it looks like you're reusing the same array when you want distinct arrays instead. Specifically this line:
#build_arr << swap(arr, i)
is always passing the same 'arr' to swap.
So first time, it swaps the 1 and the 2 to give you [2, 1, 3]
Second time, it swaps the 1 and the 3 give you [2, 3, 1]
You push the same array onto #build_arr twice, which is why it repeats.

Why is my concatenation messing up when I pass in an array?

I wrote a method to permute an array (I realize Ruby comes with a permutation function, but I wanted to practice algorithms). I'm encountering a really weird error and have no idea why this is happening.
Here's my code:
def permute(arr)
permutation(arr.sort)
end
def permutation(arr, result=[])
k = nil
result += [arr]
(arr.length-1).times do |i|
if arr[i] < arr[i+1]
k = i
end
end
if k.nil?
return result
else
l = -1
arr.length.times do |i|
if arr[k] < arr[i]
l = i
end
l = nil if l == -1
end
arr[k], arr[l] = arr[l], arr[k]
arr = arr[0..k] + arr[k+1..-1].reverse
return permutation(arr, result)
end
end
The method is recursive, and on each successive call I concatenate arr to my result variable with result += [arr] because I want the method to return a nested array, e.g. [[1, 2, 3], [1, 3, 2]..]
However, when I call this method it gives me a completely weird result.
permute([1,2,3])
=> [[1, 3, 2], [2, 3, 1], [2, 3, 1], [3, 2, 1], [3, 2, 1], [3, 2, 1]]
Why are last three results all [3,2,1]? And the other arrays aren't correct either. The really strange thing is that I can fix this by changing my concatenation to result += arr. With this change, I get the following:
permute([1,2,3])
=> [1, 2, 3, 1, 3, 2, 2, 1, 3, 2, 3, 1, 3, 1, 2, 3, 2, 1]
#I know that I can get my desired nested array like so, but that's beside the point
[1, 2, 3, 1, 3, 2, 2, 1, 3, 2, 3, 1, 3, 1, 2, 3, 2, 1].each_slice(3).to_a
=> [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
I don't get the nested arrays that I want, but the output gives me the correct permutation. Why is it working correctly now, but not which I was using result += [arr]? Is this a Ruby bug, or am I missing something here?
You're being bit by a common ruby mistake - you're modifying the original array since the 'arr' argument to permutation() is a reference to the array
Try changing:
result += [arr]
to:
result += [arr.dup]
And then presto!
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
(Incidentally, you're still monkeying with the original 'arr' values with this solution and should probably clean that up)

Resources