Assume I have in Ruby an Enumerable coll
, and the following code (which of course does not produce the desired result):
# WRONG approach
def f(coll)
pivot = coll.find do
|element|
t = element.calculate
t > 0
end
pivot && t # Return the calculated value
end
This does not work, since each block has its own scope for locals, and the t of the inner block is different from the t of the outer block. What is the most elegant way to fix it? I can think of the following possibilities:
- Recalculte the value for the found element
def f(coll)
pivot = coll.find do
|element|
element.calculate > 0
end
pivot && pivot.calculate
end
Drawback: The last calculation is unnecessarily done twice.
- Use a global variable
def f(coll)
pivot = coll.find do
|element|
@t = element.calculate
@t > 0
end
pivot && @t
end
Drawback: It's ugly design.
- Perform the
find
on the calculated values:
def f(coll)
coll.map(&:calculate).find {|x| x>0}
end
Drawback: Unnecessary calculations
Is there any better way to do it, perhaps using binding
to make the local variable accessible inside the block? If so, how can it be done?
Assume I have in Ruby an Enumerable coll
, and the following code (which of course does not produce the desired result):
# WRONG approach
def f(coll)
pivot = coll.find do
|element|
t = element.calculate
t > 0
end
pivot && t # Return the calculated value
end
This does not work, since each block has its own scope for locals, and the t of the inner block is different from the t of the outer block. What is the most elegant way to fix it? I can think of the following possibilities:
- Recalculte the value for the found element
def f(coll)
pivot = coll.find do
|element|
element.calculate > 0
end
pivot && pivot.calculate
end
Drawback: The last calculation is unnecessarily done twice.
- Use a global variable
def f(coll)
pivot = coll.find do
|element|
@t = element.calculate
@t > 0
end
pivot && @t
end
Drawback: It's ugly design.
- Perform the
find
on the calculated values:
def f(coll)
coll.map(&:calculate).find {|x| x>0}
end
Drawback: Unnecessary calculations
Is there any better way to do it, perhaps using binding
to make the local variable accessible inside the block? If so, how can it be done?
3 Answers
Reset to default 7I would recommend going about it as follows:
def f(coll)
coll.lazy.filter_map do |element|
# could be element.calculate.then {|t| t if t.positive? }
t = element.calculate
t if t.positive?
end.first
end
This uses an Enumerator::Lazy
in combination with filter_map
and first
allowing us to return the first element where the block result is not nil
, which due to the if t.positive?
will be the first calculation where calculate
returns a positive number.
The simplest way to have available a value outside a block is to use a local variable declared (by initialising) outside the block:
t = nil
call.find do |element|
t = element.calculate
t > 0
end
# here t is available
There is a more complex but may be more idiomatic/functional way suited for your case - to calculate t
lazily and using lazy #zip
find a pair of element
and t
:
ts = col.lazy.map { |e| e.calculate }
element, t = enum.lazy.zip(ts).find { |e, t| t < 0 }
break
or return
are also viable options:
def f(coll)
result = coll.find do |element|
t = element.calculate
break t if t > 0
end
result
end
def f(coll)
coll.find do |element|
t = element.calculate
return t if t > 0
end
end
lazy
to your last approach:coll.lazy.map(&:calculate).find { |x| x > 0 }
. – Stefan Commented Feb 14 at 17:22