using predefined array as an argument in a case block - ruby

im new to ruby but i would like to create a case block which uses arrays (or something similar as the argument)
here is what i have in mind
thirty_one_days_month = [1, 3, 5, 7, 8, 10, 12]
thirty_days_month = [4, 6, 9, 11]
case month
when thirty_one_days_month #instead of 1, 3, 5, 7, 8, 10, 12
#code
when thirty_days_month #instead 4, 6, 9, 11
#code
i know this code wont work but is this at all possible?

Use the splat operator:
case month
when *thirty_one_days_month
#code
when *thirty_days_month
#code
end
Anyway, that's how I'd write it:
days_by_month = {1 => 31, 2 => 28, ...}
case days_by_month[month]
when 31
# code
when 30
# code
end

You can use a case statement like this:
case
when thirty_one_days_month.include?(month)
puts "31 day month"
when thirty_days_month.include?(month)
puts "30 day month"
else
puts "February"
end

Related

Unexpected output when using `select` [duplicate]

This question already has an answer here:
Ruby block and unparenthesized arguments
(1 answer)
Closed 2 years ago.
I'm going through a tutorial on Ruby that explains the neat construction the select method provides. As per that, the three print statements in the following code (filtering out even numbers) should produce an identical output:
numbers = (1..20).to_a
p numbers.select(&:even?)
p numbers.select { |x| x.even? }
p numbers.select do |x| x.even? end
When I run it, though, I get:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
#<Enumerator: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]:select>
Clearly, the third statement is off, even though it's impossible to tell why. It's just the curly braces replaced with the do-end block so it shouldn't change anything.
I'm on Ruby 2.5 so I guess either the tutorial was running some other version and something has changed? Or maybe there's some subtlety here that I'm not able to put my finger on.
p numbers.select do |x| x.even? end
is called in this way
p(numbers.select) do |x| x.even? end
The block is passed to p, not to select as you'd expect, p just ignores it
In the second case this doesn't happen because the block with {} has higher precedence than the method call. Instead, block with do-end has lower precedence than the method call.
The second case looks like this instead
p(numbers.select { |x| x.even? })

Ruby loop through each element in an array n times

I've had a tough time finding an exact example of this.
If I have an array that contains 5 elements. E.g.
list = [5, 8, 10, 11, 15]
I would like to fetch what would be the 8th (for example) element of that array if it were to be looped. I don't want to just duplicate the array and fetch the 8th element because the nth element may change
Essentially the 8th element should be the number 10.
Any clean way to do this?
Math to the rescue
list[(8 % list.length) - 1]
A link about this modulo operator that we love
This should do:
def fetch_cycled_at_position(ary, num)
ary[(num % ary.length) - 1]
end
ary = _
=> [5, 8, 10, 11, 15]
fetch_cycled_at_position(ary, 1) # Fetch first element
=> 5
fetch_cycled_at_position(ary, 5) # Fetch 5th element
=> 15
fetch_cycled_at_position(ary, 8) # Fetch 8th element
=> 10
You could use rotate:
[5, 8, 10, 11, 15].rotate(7).first
#=> 10
It's 7 because arrays are zero based.
Just out of curiosity using Array#cycle:
[5, 8, 10, 11, 15].cycle.take(8).last
This is quite inefficient but fancy.
I ran these in my irb to get the output,
irb(main):006:0> list = [5, 8, 10, 11, 15]
=> [5, 8, 10, 11, 15]
irb(main):007:0> list[(8 % list.length) - 1]
=> 10
hope it will help you.

Ruby iterate over hours

Let's say I have some user input with start and end hours:
start = 09:00
end = 01:00
How do I display all the hours between those 2? So from 09 to 23, 0, and then to 1.
There are easy cases:
start = 01:00
end = 04:00
That's just a matter of
((start_hour.to_i)..(end_hour.to_i)).select { |hour| }
This can be solved with a custom Enumerator implementation:
def hours(from, to)
Enumerator.new do |y|
while (from != to)
y << from
from += 1
from %= 24
end
y << from
end
end
That gives you something you can use like this:
hours(9, 1).each do |hour|
puts hour
end
Or if you want an Array:
hours(9,1).to_a
#=> [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1]
You could do a oneliner (0..23).to_a.rotate(start_h)[0...end_h - start_h]
def hours_between(start_h, end_h)
(0..23).to_a.rotate(start_h)[0...end_h - start_h]
end
hours_between(1, 4)
# [1, 2, 3]
hours_between(4, 4)
# []
hours_between(23, 8)
# [23, 0, 1, 2, 3, 4, 5, 6, 7]
Don't forget to sanitize the input (That they are number between 0 and 23) :)
If you want the finishing hour use .. instead of ... => [0..end_h - start_h]
If you care about performance or want something evaluated lazily you can also do the following (reading the code is really clear):
(0..23).lazy.map {|h| (h + start_h) % 24 }.take_while { |h| h != end_h }
With a simple condition:
def hours(from, to)
if from <= to
(from..to).to_a
else
(from..23).to_a + (0..to).to_a
end
end
hours(1, 9)
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
hours(9, 1)
#=> [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 0, 1]
You could also use the shorter, but more cryptic [*from..23, *0..to] notation.
https://stackoverflow.com/a/6784628/3012550 shows how to iterate over the number of hours in the distance between two times.
I would use that, and at each iteration use start + i.hours
def hours(number)
number * 60 * 60
end
((end_time - start_time) / hours(1)).round.times do |i|
print start_time + hours(i)
end

Every Other 2 Items in Array

I need a ruby formula to create an array of integers. The array must be every other 2 numbers as follows.
[2, 3, 6, 7, 10, 11, 14, 15, 18, 19...]
I have read a lot about how I can do every other number or multiples, but I am not sure of the best way to achieve what I need.
Here's an approach that works on any array.
def every_other_two arr
arr.select.with_index do |_, idx|
idx % 4 > 1
end
end
every_other_two((0...20).to_a) # => [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
# it works on any array
every_other_two %w{one two three four five six} # => ["three", "four"]
array = []
#Change 100000 to whatever is your upper limit
100000.times do |i|
array << i if i%4 > 1
end
This code works for any start number to any end limit
i = 3
j = 19
x =[]
(i...j).each do |y|
x << y if (y-i)%4<2
end
puts x
this should work
For fun, using lazy enumerables (requires Ruby 2.0 or gem enumerable-lazy):
(2..Float::INFINITY).step(4).lazy.map(&:to_i).flat_map { |x| [x, x+1] }.first(8)
#=> => [2, 3, 6, 7, 10, 11, 14, 15]
here's a solution that works with infinite streams:
enum = Enumerator.new do |y|
(2...1/0.0).each_slice(4) do |slice|
slice[0 .. 1].each { |n| y.yield(n) }
end
end
enum.first(10) #=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
enum.each do |n|
puts n
end
Single Liner:
(0..20).to_a.reduce([0,[]]){|(count,arr),ele| arr << ele if count%4 > 1;
[count+1,arr] }.last
Explanation:
Starts the reduce look with 0,[] in count,arr vars
Add current element to array if condition satisfied. Block returns increment and arr for the next iteration.
I agree though that it is not so much of a single liner though and a bit complex looking.
Here's a slightly more general version of Sergio's fine answer
module Enumerable
def every_other(slice=1)
mod = slice*2
res = select.with_index { |_, i| i % mod >= slice }
block_given? ? res.map{|x| yield(x)} : res
end
end
irb> (0...20).every_other
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> (0...20).every_other(2)
=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
irb> (0...20).every_other(3)
=> [3, 4, 5, 9, 10, 11, 15, 16, 17]
irb> (0...20).every_other(5) {|v| v*10 }
=> [50, 60, 70, 80, 90, 150, 160, 170, 180, 190]

How to interleave arrays of different length in Ruby

If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?
Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end
Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]

Resources