Ruby print array with variable name - ruby

I want to parse a array in such way it gives following output
arr1 = (1..5).to_a
arr2 = (4..10).to_a
arr3 = (10..20).to_a
(1..3).map do |i|
puts arr#{i} # It will throw an error, I am looking a way to achieve this.
end
Need to achieve above result in ruby.

You can do almost anything in Ruby. To get the value of a local variable in the current binding, you'd use local_variable_get:
arr1 = (1..5).to_a
arr2 = (4..10).to_a
arr3 = (10..20).to_a
(1..3).each do |i|
puts binding.local_variable_get("arr#{i}")
end
But that's cumbersome and error prone.
If you want to iterate over objects, put them in a collection. If you want the objects to have a certain label (like your variable name), use a hash:
arrays = {
arr1: (1..5).to_a,
arr2: (4..10).to_a,
arr3: (10..20).to_a
}
arrays.each do |name, values|
puts "#{name} = #{values}"
end
Output:
arr1 = [1, 2, 3, 4, 5]
arr2 = [4, 5, 6, 7, 8, 9, 10]
arr3 = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
If the names are not relevant, use an array as shown in max pleaner's answer.

The quick and dirty way is to use eval:
(1..3).map do |i|
puts eval("arr#{i}")
end
but you should not do this in your code, it's non-idiomatic, slower, unsafe, and is not properly using data structures. A better way is to move the arrays into a parent array:
arrays = [
(1..5).to_a,
(4..10).to_a,
(10..20).to_a
]
arrays.each { |arr| puts arr }

Related

How to use collect and include for multidimensional array

I have:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
I want to replace elements in array1 that are included in #student_ids with 'X'. I want to see:
[['X','X','X',4,5],[7,8,9,10],[11,12,13,14]]
I have code that is intended to do this:
array1.collect! do |i|
if i.include?(#student_ids) #
i[i.index(#student_ids)] = 'X'; i # I want to replace all with X
else
i
end
end
If #student_ids is 1, then it works, but if #student_ids has more than one element such as 1,2,3, it raises errors. Any help?
It's faster to use a hash or a set than to repeatedly test [1,2,3].include?(n).
arr = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
ids = [1,2,3]
Use a hash
h = ids.product(["X"]).to_h
#=> {1=>"X", 2=>"X", 3=>"X"}
arr.map { |a| a.map { |n| h.fetch(n, n) } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
See Hash#fetch.
Use a set
require 'set'
ids = ids.to_set
#=> #<Set: {1, 2, 3}>
arr.map { |a| a.map { |n| ids.include?(n) ? "X" : n } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
Replace both maps with map! if the array is to be modified in place (mutated).
Try following, (taking #student_ids = [1, 2, 3])
array1.inject([]) { |m,a| m << a.map { |x| #student_ids.include?(x) ? 'X' : x } }
# => [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
You can use each_with_index and replace the item you want:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
array1.each_with_index do |sub_array, index|
sub_array.each_with_index do |item, index2|
array1[index][index2] = 'X' if #student_ids.include?(item)
end
end
You can do the following:
def remove_student_ids(arr)
arr.each_with_index do |value, index|
arr[index] = 'X' if #student_ids.include?(value) }
end
end
array1.map{ |sub_arr| remove_student_ids(sub_arr)}

Ruby Getting a max value out of a newly created array in one function

I want my function to return the longest Array within a nested array (including the array itself) so
nested_ary = [[1,2],[[1,2,[[1,2,3,4,[5],6,7,11]]]],[1,[2]]
deep_max(nested_ary)
=> [1,2,3,4,[5],6,7,11]
simple_ary = [1,2,3,4,5]
deep_max(simple_ary)
=> returns: [1,2,3,4,5]
I created a function to collect all arrays. I have to get the max value in another function.
my code:
def deep_max(ary)
ary.inject([ary]) { |memo, elem|
if elem.is_a?(Array)
memo.concat(deep_max(elem))
else
memo
end }
end
This gives me what I want:
deep_max(nested_ary).max_by{ |elem| elem.size }
Is there a way to get this max inside of the function?
def deep_max(arr)
biggest_so_far = arr
arr.each do |e|
if e.is_a?(Array)
candidate = deep_max(e)
biggest_so_far = candidate if candidate.size > biggest_so_far.size
end
end
biggest_so_far
end
deep_max [[1, 2], [[1, 2, [[1, 2, 3, 4, [5], 6, 7, 11]]]], [1, [2]]]
#=> [1, 2, 3, 4, [5], 6, 7, 11]
You can unroll it:
def deep_max(ary)
arys = []
ary = [ary]
until ary.empty?
elem = ary.pop
if elem.is_a?(Array)
ary.push(*elem)
arys.push(elem)
end
end
arys.max_by(&:size)
end
Or you can cheat, by introducing an optional parameter that changes how your recursion works on top level vs how it behaves down the rabbit hole.

error in modulus method in ruby

I am trying to write a method that takes in an array as an argument and returns an array of the numbers in the argument that are have both an even index number and an even value. I am not sure why, but it is giving me the error "undefined method %" in line 5. Can someone explain how I can fix this?
def odd_value_and_position(array)
newArray=[] #create new array
i=0 #i=0
while i <= array.length #loop while
newArray.push(array[i]) if array[i] % 2 != 0
i = i + 2
end
return newArray
end
puts odd_value_and_position([0,1,2,3,4,5])
Another way to do this:
def evens arr
arr.select.with_index { |e,i| e.even? && i.even? }
end
evens [0,1,2,3,4,5] #=> [0,2,4]
When i is equal to array.length, array[i] is nil.
What is nil % 2? It is undefined.
def odd_value_and_position(array)
newArray=[] #create new array
i=0 #i=0
while i < array.length #loop while
newArray.push(array[i]) if array[i] % 2 != 0
i = i + 2
end
return newArray
end
puts odd_value_and_position([0,1,2,3,4,5]) #=> []
puts odd_value_and_position([1,2,3,4,5]) #=> [1,3,5]
Due to the fact that the first element in a Ruby Array has 0 as index, I'm not sure you get the result you expected. See examples in code.
A more Rubyish example would be :
def odd_value_and_position(array)
array.select.with_index(1){|x,i| x.odd? && i.odd?}
end
puts odd_value_and_position([1,2,3,4,5]) #=> [1,3,5]
If I understand the question right, I'd go with something like:
def some_method_name(array)
array.select.with_index { |*ij|
ij.all?(&:even?)
}
end
puts some_method_name([0, 1, 2, 3, 4, 5, 10, 13, 21, 22, 30])
# >> 0
# >> 2
# >> 4
# >> 10
# >> 30
Here's what it's doing:
def some_method_name(array)
array.select.with_index { |*ij|
ij # => [0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [10, 6], [13, 7], [21, 8], [22, 9], [30, 10]
ij.all?(&:even?) # => true, false, true, false, true, false, true, false, false, false, true
}
end
puts some_method_name([0, 1, 2, 3, 4, 5, 10, 13, 21, 22, 30])
# >> 0
# >> 2
# >> 4
# >> 10
# >> 30
There are a couple problems with the original code.
Using while loops easily leads to problems with off-by-one errors, or loops that never trigger, or loops that never end.
To combat that in Ruby, we use each and map, select, reject or similar iterators to loop over the array, and process each element in turn, then base the logic on that.
array.select is looking at each element and applying the logic in the block, looking for "truthy" results. with_index adds the index of the iteration as a second value passed into the block. *id turns the two values being passed in into an array, making it easy to apply all? and its even? test. If even? returns true to both then all? triggers and returns true again which signals to select to return that element of the array.

Return an array between start A and B

$array = []
def range(start_position,end_position)
for i in start_position..end_position
$array.push(i)
puts $array
end
return $array
end
range(1,10)
I was wondering why exactly my array isnt returning. Clearly when I do puts $array, 1-10 is being inserted, but when I call my function I want the array to be returned. Any thoughts, I'm reading through documentation but can't find what i've done wrong or if I have made any syntax errors.
It returns an array, but you are not doing anything with it, so it is discarded. You could print it (with p range(1,10) or puts range(1,10) ) or assign it to a variable for later use ( result = range(1,10) ). Which is the same as result = (1..10).to_a, not using your method at all.
Your code is essentially correct you just need to omit the puts $array within your for block. And also no need for the optional return keyword. So fixing your code we would have:
$array = []
def range(start_position,end_position)
for i in start_position..end_position
$array.push(i)
end
$array
end
p range(1,10) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Kernel#p performs the Kernel#inspect method which as stated in the documentation provides a human-readable representation of the object in question. In Ruby it would be much better to do something like this perhaps:
def range(start_position,end_position)
(start_position..end_position).map {|i| i }
end
p range(1,10) #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Your array does return, but you don't do anything with the return value. Try saving it to a variable or print the result:
print range(1, 3) # => [1, 2, 3]
If you're looking for a simpler way to implement this function, you could use Ruby ranges to do it, something like:
def range (start_position, end_position)
# Create a range and convert it to an array
# We can omit return
(start_position .. end_position).to_a
end
print range(1, 3) # => [1, 2, 3]
Also beware your function will only work the first time, because you're using a global variable as your starting point. Check what happens when you call it multiple times:
print range(1, 3) # => [1, 2, 3]
print range(1, 3) # => [1, 2, 3, 1, 2, 3]

Can't iterate over Time objects in Ruby

I am writing an appointment form that will let the user choose a date. It will then take the date and check against a Google Calendar what time slots are available for that date within a range of 30 minutes time intervals from 10:00am to 5:00pm.
Within my Calendar class, I have an available_times method:
def available_times(appointment_date)
appointment_date_events = calendar.events.select { |event| Date.parse(event.start_time) == appointment_date }
conflicts = appointment_date_events.map { |event| [Time.parse(event.start_time), Time.parse(event.end_time)] }
results = resolve_time_conflicts(conflicts)
end
This method takes a date and grabs the start_time and end_time for each event on that date. It then calls resolve_time_conflicts(conflicts):
def resolve_time_conflicts(conflicts)
start_time = Time.parse('10:00am')
available_times = []
14.times do |interval_multiple|
appointment_time = (start_time + interval_multiple * (30 * 60))
available_times << appointment_time unless conflicts.each{ |conflict| (conflict[0]..conflict[1]).include?(appointment_time)}
end
available_times
end
A 'Can't iterate over Time' error is being thrown when I attempt to iterate over the conflicts array. I tried to call to_enum on the conflicts array but am still getting the same error.
All of the other questions I saw on SO were referencing the step method, which doesn't seem applicable to this case.
Update:
Thanks #caryswoveland and #fivedigit. I combined both of your answers, which were very helpful for different aspects of my solution:
def available_times(appointment_date)
appointment_date_events = calendar.events.select { |event| Date.parse(event.start_time) == appointment_date }
conflicts = appointment_date_events.map { |event| DateTime.parse(event.start_time)..DateTime.parse(event.end_time) }
results = resolve_time_conflicts(conflicts)
end
def resolve_time_conflicts(conflicts)
date = conflicts.first.first
start_time = DateTime.new(date.year, date.month, date.day, 10, 00).change(offset: date.zone)
available_times = []
14.times do |interval_multiple|
appointment_time = (start_time + ((interval_multiple * 30).minutes))
available_times << appointment_time unless conflicts.any? { |conflict| conflict.cover?(appointment_time)}
end
available_times
end
Exception
#fivedigit has explained why the exception was raised.
Other problems
You need any? where you have each:
appointment_times = []
#=> []
appointment = 4
#=> 4
conflicts = [(1..3), (5..7)]
#=> [1..3, 5..7]
appointment_times << 5 unless conflicts.each { |r| r.cover?(appointment) }
#=> nil
appointment_times
#=> []
appointment_times << 5 unless conflicts.any? { |r| r.include?(appointment) }
#=> [5]
appointment_times
#=> [5]
I suggest you covert appointment_time to a Time object, make conflicts and array of elements [start_time, end_time] and then compare appointment_time to the endpoints:
...unless conflicts.any?{ |start_time, end_time|
start_time <= appointment_time && appointment_time <= end_time }
Aside: Range#include? only looks at endpoints (as Range#cover? does) when the endpoints are "numeric". Range#include? need only look at endpoints when they are Time objects, but I don't know if Ruby regards Time objects as "numeric". I guess one could look at the source code. Anybody know offhand?
Alternative approach
I would like to suggest a different way to implement your method. I will do so with an example.
Suppose appointments were in blocks of 15 minutes, with the first block being 10:00am-10:15am and the last 4:45pm-5:00pm. (blocks could be shorter, of course, as small as 1 second in duration.)
Let 10:00am-10:15am be block 0, 10:15am-10:30am be block 1, and so on, until block 27, 4:45pm-5:00pm.
Next, express conflicts as an array of block ranges, given by [start, end]. Suppose there were appointments at:
10:45am-11:30am (blocks 3, 4 and 5)
1:00pm- 1:30pm (blocks 12 and 13)
2:15pm- 3:30pm (blocks 17, 18 and 19)
Then:
conflicts = [[3,5], [12,13], [17,19]]
You must write a method reserved_blocks(appointment_date) that returns conflicts.
The remaining code is as follows:
BLOCKS = 28
MINUTES = ["00", "15", "30", "45"]
BLOCK_TO_TIME = (BLOCKS-1).times.map { |i|
"#{i<12 ? 10+i/4 : (i-8)/4}:#{MINUTES[i%4]}#{i<8 ? 'am' : 'pm'}" }
#=> ["10:00am", "10:15am", "10:30am", "10:45am",
# "11:00am", "11:15am", "11:30am", "11:45am",
# "12:00pm", "12:15pm", "12:30pm", "12:45pm",
# "1:00pm", "1:15pm", "1:30pm", "1:45pm",
# "2:00pm", "2:15pm", "2:30pm", "2:45pm",
# "3:00pm", "3:15pm", "3:30pm", "3:45pm",
# "4:00pm", "4:15pm", "4:30pm", "4:45pm"]
def available_times(appointment_date)
available = [*(0..BLOCKS-1)]-reserved_blocks(appointment_date)
.flat_map { |s,e| (s..e).to_a }
last = -2 # any value will do, can even remove statement
test = false
available.chunk { |b| (test=!test) if b > last+1; last = b; test }
.map { |_,a| [BLOCK_TO_TIME[a.first],
(a.last < BLOCKS-1) ? BLOCK_TO_TIME[a.last+1] : "5:00pm"] }
end
def reserved_blocks(date) # stub for demonstration.
[[3,5], [12,13], [17,19]]
end
Let's see what we get:
available_times("anything")
#=> [["10:00am", "10:45am"],
# ["11:30am", "1:00pm"],
# [ "1:45pm", "2:15pm"],
# [ "3:00pm", "5:00pm"]]
Explanation
Here is what's happening:
appointment_date = "anything" # dummy for demonstration
all_blocks = [*(0..BLOCKS-1)]
#=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
# 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
reserved_ranges = reserved_blocks(appointment_date)
#=> [[3, 5], [12, 13], [17, 19]]
reserved = reserved_ranges.flat_map { |s,e| (s..e).to_a }
#=> [3, 4, 5, 12, 13, 17, 18, 19]
available = ALL_BLOCKS - reserved
#=> [0, 1, 2, 6, 7, 8, 9, 10, 11, 14, 15, 16, 20, 21, 22, 23, 24, 25, 26, 27]
last = -2
test = false
enum1 = available.chunk { |b| (test=!test) if b > last+1; last = b; test }
#=> #<Enumerator: #<Enumerator::Generator:0x00000103063570>:each>
We can convert it to an array to see what values it would pass into the block if map did not follow:
enum1.to_a
#=> [[true, [0, 1, 2]],
# [false, [6, 7, 8, 9, 10, 11]],
# [true, [14, 15, 16]],
# [false, [20, 21, 22, 23, 24, 25, 26, 27]]]
Enumerable#chunk groups consecutive values of the enumerator. It does so by grouping on the value of test and flipping its value between true and false whenever a non-consecutive value is encountered.
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: (cont.)
#<Enumerator::Generator:0x00000103063570>:each>:map>
enum2.to_a
#=> [[true, [0, 1, 2]],
# [false, [6, 7, 8, 9, 10, 11]],
# [true, [14, 15, 16]],
# [false, [20, 21, 22, 23, 24, 25, 26, 27]]]
You might think of enum2 as a "compound" enumerator.
Lastly, we convert the second element of each value of enum2 that is passed into the block (the block variable a, which equals [0,1,2] for the first element passed) to a range expressed as a 12-hour time. The first element of each value of enum2 (true or false) is not used, so so I've replaced its block variable with an underscore. This provides the desired result:
enum2.each { |_,a|[BLOCK_TO_TIME[a.first], \
(a.last < BLOCKS-1) ? BLOCK_TO_TIME[a.last+1] : "5:00pm"] }
#=> [["10:00am", "10:45am"],
# ["11:30am", "1:00pm"],
# [ "1:45pm", "2:15pm"],
# [ "3:00pm", "5:00pm"]]
The issue comes from this bit:
(conflict[0]..conflict[1]).include?(appointment_time)
# TypeError: can't iterate from Time
You're creating a range of times and then checking if appointment_time falls within the range. This is what causes the error you're experiencing.
Instead of include?, you should use cover?:
(conflict[0]..conflict[1]).cover?(appointment_time)
This assumes that conflict[0] is the earliest time.
Convert your range from a range of times to a range of integers:
range = (conflict[0].to_i..conflict[1].to_i)
Then use the === operator as you used the include?:
conflict === appointment_time
EDIT: You can also obviously convert appointment_time to integer and still use include? since the range is now just an integer range.

Resources