I have found a question: Nested Loops Ruby and solved it, but the solution looks ugly (although works):
puts (1..10).map { |i| (i..10).step(i).inject(0) { |memo, obj| memo = memo | 2**(obj-1) } }.inject(0) { |memo, obj| memo = memo ^ obj}
Rewritten to multiline, but keeping curly braces:
puts (1..10).map { |i|
(i..10).step(i).inject(0) { |memo, obj|
memo = memo | 2**(obj-1)
}
}.inject { |memo, obj|
memo = memo ^ obj
}
I tried to rewrite it into multiline do-end blocks to make it more readable (knowing about the precedence difference between {}
and do-end
), but I get an error (I just changed the last braces):
puts (1..10).map { |i|
(i..10).step(i).inject(0) { |memo, obj|
memo = memo | 2**(obj-1)
}
}.inject do |memo, obj|
memo = memo ^ obj
end.to_s(2)
../../bitflipping.rb:5:in 'each': no block given (LocalJumpError)
from ../../bitflipping.rb:5:in 'inject'
from ../../bitflipping.rb:5:in ''
Is it possible to rewrite this with do-end
? I think there is a precedence problem, how can I regroup them so for example inject
at the end gets the block properly?
try making it into a method, maybe? although as sawa says, map{|x| x}
doesn't do anything
def my_method
first_step = (1..10).map do |i|
(i..10).step(i).map { |x| x}.inject(0) { |memo, obj| memo = memo | 2**(obj-1) }
end
second_step = first_step.inject { |memo, obj| memo = memo ^ obj}.to_s(2)
return second_step
end
puts my_method
The syntactical construct that triggers the problem is the puts
without parentheses.
You can fix the problem by refactoring the code so you're assigning the result to a variable (e.g. result
) first, then doing puts result
afterward.
Alternate solution is to wrap the entire expression in parentheses.
Here's a slimmed down reproduction of the scenarios:
# OK, because assignment has lower precedence than do/end
result = (1..10).inject do |memo, obj|
memo + obj
end
puts result
# OK because the outer parentheses resolves the ambiguity
puts(
(1..10).inject do |memo, obj|
memo + obj
end
)
# ERROR: no block given
puts (1..10).inject do |memo, obj|
memo + obj
end
The error happens because a do
/end
block has lower precedence than a method call (without parentheses).
The ERROR case is equivalent to:
puts( (1..10).inject ) do |memo, obj|
memo + obj
end
... in other words, you're passing the block to puts
instead of inject
. And inject
(at least in this case) fails because it requires a block.