In ruby we can do something like:
stuff_in_trash.detect(&:eatable?)
=> :pack_of_peanuts
stuff_in_trash.detect(&:drinkable?)
=> nil
But what if we are interested in the value of the block the first time it is truthy, rather than the first item for which the block takes a truthy value?
That is convert the following code:
def try_to_make_artwork_from(enumerable)
enumerable.each do |item|
result = make_artwork_from item
return result if result
end
nil
end
To something like:
def try_to_make_artwork_from(enumerable)
enumerable.try_with { |item| make_artwork_from item }
end
What is desirable in the initial code is:
nil
if the block never takes a truthy value;make_artwork_from
again (let us say it is not guaranteed to return the same result the next time you call it).What is not so desirable, is that uses result
three times, yet it has nothing to do with the story.
EDIT: Sorry, the initial implementation was incorrect, it needed to return nil
in the case the block value was never true.
enumerable.lazy.map(&:block).detect(&:itself)
does the job, but is the simplest way? Is it efficient compare to simply using an each
and caching the value?
it does the job, but is the simplest way? Is it efficient compare to simply using a each and caching the value?
We can define this method :
def first_truthy_block(enumerable, &block)
enumerable.lazy.map(&block).find(&:itself)
end
Here in action :
array = [0,1,2,3,4,:x5,'abc']
puts first_truthy_block(array) { |x|
if x ** 2 > 10 then
"ARTWORK with #{x}!!!"
end
}
#=> ARTWORK with 4!!!
Could it be simpler?
enumerable
is needed, it's the object you're working on.lazy
is needed, it wouldn't stop as soon as possible, and would throw an exception with :x5**2
.With standard Enumerable
methods, I don't see how it could be any simpler.
It is slower than your each
method. It does basically the same thing and should have the same complexity but it does use more method calls and creates more objects :
require 'fruity'
def first_truthy_block_lazy(enumerable, &block)
enumerable.lazy.map(&block).find(&:itself)
end
def first_truthy_block_each(enumerable, &block)
enumerable.each do |item|
result = block.call(item)
return result if result
end
nil
end
big_array = Array.new(10_000){rand(4)} + [5] + Array.new(10_000){rand(20)} + [:x, :y, 'z']
compare do
_lazy_map do
first_truthy_block_lazy(big_array) { |x|
if x ** 2 > 10 then
"ARTWORK with #{x}!!!"
end
}
end
_each do
first_truthy_block_each(big_array) { |x|
if x ** 2 > 10 then
"ARTWORK with #{x}!!!"
end
}
end
end
fruity returns :
Running each test once. Test will take about 1 second.
_each is faster than _lazy_map by 3x ± 0.1