Unexpected output when using `select` [duplicate] - ruby

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? })

Related

What's the reason the second code won't return what the first code successfully returns

I was doing a quick read up on arrays and some basic methods. And one of the exercise questions at the end of the reading gave me an array and asked to get the following output
=> [10, 8, 4, 2]
Here's the array:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
solution:1
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers = numbers.select { |number| number.even? }.reverse
numbers.delete(6)
p numbers
But my question to you is why would the above code return the correct output but the following code won't?
solution: 2
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers = numbers.select { |number| number.even? }
numbers.delete(6)
numbers.reverse
p numbers
I understand it's not the most fluent, but when I try to solve these exercises it's easier for me to separate everything and then clean up the code.
I expected it to pull the even numbers delete 6 from them and then print the reversed array.
Instead it pulls the even numbers, deletes 6, and prints the even numbers. Completely skips the .reverse
As max says, .reverse doesn't change the array. Try, instead:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers = numbers.select { |number| number.even? }
numbers.delete(6)
numbers.reverse!
p numbers
=> [10, 8, 4, 2]
As other commenters have mentioned, .reverse doesn't change the array.
You either have to declare numbers.reverse as a new variable (i.e. reversed_numbers = numbers.reverse) or use numbers.reverse! (as demonstrated by jvillian) to change the value of the numbers variable itself at invocation.
Between the two, the latter method is more suitable.
Hope this helped!

Can I have a ruby block inside another ruby block?

I have a hash whose keys are a range of integers (lets say [1..5]) and its corresponding 5 values are all nil. I have also an array of integers (lets say [1,2,3,4,5]. What I want to do is very specific: I want to take every single key and add it to every single of the array elements, giving me a hash that has the original keys, but has now for values the entire shifted array.
After spending a few hours I have concluded that this is impossible through a really laconic expression, because it is leading to .each shadowing statements.
I think that the only way to go through with this is to create 5 almost identical methods and call them separately.
def a1
array.each do |x|
x+1
end
end
def a2
array.each do |x|
x+2
end
end
and so on..
The end product I want to achieve is this:
{1=>[2,3,4,5,6],2=>[3,4,5,6,7],3=>[4,5,6,7,8],4=>[5,6,7,8,9],5=>[6,7,8,9,10]}
It feels like there should be a more DRY way to achieve this. Any ideas?
Assuming these initial conditions:
h = {1=>nil, 2=>nil, 3=>nil, 4=>nil, 5=>nil}
arr = [1,2,3,4,5]
...it's pretty straightforward:
h.keys.each do |key|
h[key] = arr.map {|i| i+key}
end
# h is now: {1=>[2, 3, 4, 5, 6], 2=>[3, 4, 5, 6, 7], 3=>[4, 5, 6, 7, 8], 4=>[5, 6, 7, 8, 9], 5=>[6, 7, 8, 9, 10]}
(However, it may be that your question is about achieving the initial conditions. If so, I didn't grasp that, and I didn't worry about it; I just started with what I took to be your initial conditions and ended up with your desired result.)
Why don't you do this
h = {}
rng.each{|i| h[i] = ary.map{|j| j + i}}
That should work where rng is the range and ary is the array.
For example
h = {}
(1..5).each{|i| h[i] = [1,2,3,4,5].map{|j| j+i}}
results in
h = {1=>[2, 3, 4, 5, 6], 2=>[3, 4, 5, 6, 7], 3=>[4, 5, 6, 7, 8], 4=>[5, 6, 7, 8, 9], 5=>[6, 7, 8, 9, 10]}

I am receiving nil values in my small recursion method

The basic premise of the method is for the argument array to take a multidimensional array and snake around the matrix clockwise pushing all values into arr and returning them as a list of integers.
However,I seem to be receiving this error:
`block in snail': undefined method `reverse' for nil:NilClass (NoMethodError)
This is my method:
def snail(array)
arr = []
loop do
return arr.flatten if array.empty?
arr << array.shift
array.map {|row| arr << row.pop}
arr << array.pop.reverse
array.map {|row| arr << row.shift}
end
end
This is the test argument:
[[1,2,3],[4,5,6],[7,8,9]
This is the output i get when i edit line 5 to this:
line 5: arr << unless NilClass then array.pop.reverse end
output: [1, 2, 3, 6, 9, nil, 4, 7, 5, 8, nil, nil]
Expected return:
[1, 2, 3, 6, 9, 8, 7, 4, 5]
Why are these nil values appearing?
The problem is that on your second iteration, array becomes empty after arr << array.shift so the next two lines are executing on an empty array. The error you're seeing is because array.pop on the eighth line of the method has nothing to pop and so returns nil and nil does not support #reverse.
You need to handle that array can become depleted at any stage, not just after all four operations are executed.
def snail(array)
arr = []
until array.flatten.empty?
arr << array.shift
array.map {|row| arr << row.pop} unless array.flatten.empty?
arr << array.pop.reverse unless array.flatten.empty?
array.map {|row| arr << row.shift} unless array.flatten.empty?
end
return arr.flatten
end
Incidentally... I think the snail going back up on the left side is not correctly coded. You have to read the numbers from bottom to top, so, the last line of the loop should be...
array.reverse.map {|row| arr << row.shift} unless array.flatten.empty?
The problem doesn't show on your test argument but you'll see the issue if you try a larger matrix, like
[[1,2,3,4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
Your method gives...
[1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 5, 9, 6, 7, 11, 10]
But correct answer is...
[1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10]

using predefined array as an argument in a case block

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

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]

Resources