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]]
Related
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
So I am trying to get a solution to my two sum problem and I am stuck, I need to print the indices for the elements which add up to the target and my solution will return an element twice if it is one half of the target
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[num], idx]
end
end
end
So I don't think the problem is related to the number being 1/2 of the target, it just seems to be "if a solution is found, it returns the same index twice". For instance, using the sample set [2, 7, 11, 15]
two_sum([2, 7, 11, 15], 14) # => [2, 7, 11, 15]
So, 7 is half of 14, which is the target, and instead of returning the index 1 twice, as you suggest it would, it returns the original input array (the result of nums.each_with_index. However, if we try passing a target of 9, it behaves as you describe:
two_sum([2, 7, 11, 15], 9) # => [1, 1]
The reason for this, is because of the line:
return [num_hash[num], idx]
you have already set num into the num_hash (num_hash[num] = idx) and then you are returning both the idx and num_hash[num], which is also idx. So what you want to do is:
return [num_hash[target - num], idx]
and then to 'fix' all the elements being returned when no result is found, just return [] at the end of the method:
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[target - num], idx]
end
end
[]
end
and now:
two_sum([2, 7, 11, 15], 14) # => []
two_sum([2, 7, 11, 15], 9) # => [0, 1]
Note: you also have a problem with the code where, if you have the same number twice, it doesn't find the answer:
two_sum([2, 7, 11, 7, 15], 14) # => []
left for you to figure out, just wanted to point this out to you.
You can use the method Array#combination to advantage here.
def two_sum(nums, target)
nums.each_index.to_a.combination(2).select { |i,j| nums[i] + nums[j] == target }
end
two_sum([2, 7, 11, 15], 14)
#=> []
two_sum([2, 7, 11, 15], 9)
#=> [[0, 1]]
two_sum([2, 4, 7, 5], 9)
#=> [[0, 2], [1, 3]]
two_sum([2, 2, 2, 2], 4)
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
two_sum([2, 4, 7, 5], 8)
#=> []
For
nums = [2, 4, 7, 5]
target = 9
the steps are as follows.
a = nums.each_index
#=> #<Enumerator: [2, 4, 7, 5]:each_index>
We can see the elements that will be generated by this enumerator by converting it to an array.
b = a.to_a
#=> [0, 1, 2, 3]
Next,
c = b.combination(2)
#=> #<Enumerator: [0, 1, 2, 3]:combination(2)>
c.to_a
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
The rest is straightforward as select merely selects those pairs of indices passed to it (i,j) whose corresponding values, num[i] and num[j], sum to target.
I think what you want is ...
return [num_hash[target-num], idx]
I'm trying to transpose [[0, 1, 2], [3, 4, 5], [6, 7, 8]]. I get [[2, 5, 8], [2, 5, 8], [2, 5, 8]].
I can see what is happening with the line p transposed_arr but do not understand why this is happening. At every iteration it changes every row instead of only one.
def my_transpose(arr)
# number of rows
m = arr.count
#number of columns
n = arr[0].count
transposed_arr = Array.new(n, Array.new(m))
# loop through the rows
arr.each_with_index do |row, index1|
# loop through the colons of one row
row.each_with_index do |num, index2|
# swap indexes to transpose the initial array
transposed_arr[index2][index1] = num
p transposed_arr
end
end
transposed_arr
end
You need to make only one wee change and your method will work fine. Replace:
transposed_arr = Array.new(n, Array.new(m))
with:
transposed_arr = Array.new(n) { Array.new(m) }
The former makes transposed_arr[i] the same object (an array of size m) for all i. The latter creates a separate array of size m for each i
Case 1:
transposed_arr = Array.new(2, Array.new(2))
transposed_arr[0].object_id
#=> 70235487747860
transposed_arr[1].object_id
#=> 70235487747860
Case 2:
transposed_arr = Array.new(2) { Array.new(2) }
transposed_arr[0].object_id
#=> 70235478805680
transposed_arr[1].object_id
#=> 70235478805660
With that change your method returns:
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]
Lets supose that I have a hash:
class MyHash
H = { 1 => [1,2,3,4,5], 2 => [2,7,8,9,10] }
def self.get(id)
# code
end
end
How is the implementation for that method so that I can access the data this way?
element = MyHash.get 1
# => [1,2]
element = MyHash.get 6
# => [2,7]
element = MyHash.get 4
# => [1,5]
element = MyHash.get 5
# => [2,2]
I can write the method "manually" but maybe there is a simpler "rubyist" method to do that
You could do this:
H = { 1 => [1,2,3,4,5], 2 => [6,7,8,9,10] }
HINV = H.flat_map { |k,v| [k].product(v) }
.map.with_index { |(k,v),i| [i,[k,v]] }
.to_h
#=> {0=>[1, 1], 1=>[1, 2], 2=>[1, 3], 3=>[1, 4], 4=>[1, 5],
# 5=>[2, 6], 6=>[2, 7], 7=>[2, 8], 8=>[2, 9], 9=>[2, 10]}
class MyHash
def self.get(id)
HINV[id]
end
end
MyHash.get(1) #=> [1, 2]
MyHash.get(6) #=> [2, 7]
MyHash.get(4) #=> [1, 5]
There is, however, no need for the class MyHash:
HINV[1] #=> [1, 2]
HINV[6] #=> [2, 7]
HINV[4] #=> [1, 5]
If you don't wish the hash to be a constant:
def invert_hash_by_values_index(h)
Hash[h.flat_map { |k,v| [k].product(v) }
.map.with_index { |(k,v),i| [i,[k,v]] }]
end
h = { 1 => [1,2,3,4,5], 2 => [6,7,8,9,10] }
hinv = invert_hash_by_values_index(h)
#=> {0=>[1, 1], 1=>[1, 2], 2=>[1, 3], 3=>[1, 4], 4=>[1, 5],
# 5=>[2, 6], 6=>[2, 7], 7=>[2, 8], 8=>[2, 9], 9=>[2, 10]}
hinv[1] #=> [1, 0]
hinv[6] #=> [2, 0]
hinv[4] #=> [1, 3]
Here I've used the class method Hash::[], rather than instance method Array#to_h, the latter having been added in Ruby 2.0.
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.