在红宝石中我们可以做类似的事情:
stuff_in_trash.detect(&:eatable?)
=> :pack_of_peanuts
stuff_in_trash.detect(&:drinkable?)
=> nil
但是,如果我们第一次感兴趣的是块的价值,而不是块的真正价值的第一个项目呢?
即转换以下代码:
def try_to_make_artwork_from(enumerable)
enumerable.each do |item|
result = make_artwork_from item
return result if result
end
nil
end
类似于:
def try_to_make_artwork_from(enumerable)
enumerable.try_with { |item| make_artwork_from item }
end
初始代码中需要的是:
nil
; make_artwork_from
(让我们说下次调用时不能保证返回相同的结果)。不可取的是,使用result
三次,但它与故事无关。
编辑:抱歉,初始实现不正确,如果块值永远不为真,则需要返回nil
。
enumerable.lazy.map(&:block).detect(&:itself)
完成这项工作,但这是最简单的方法吗?与仅使用each
并缓存值相比,它是否有效?
答案 0 :(得分:3)
它完成了这项工作,但这是最简单的方法吗?比较有效吗? 只需使用每个并缓存值?
我们可以定义这个方法:
128 x 15 x 128
这里有效:
def first_truthy_block(enumerable, &block)
enumerable.lazy.map(&block).find(&:itself)
end
可能更简单吗?
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!!!
是必需的,它是您正在处理的对象。enumerable
是必需的,它不会尽快停止,并会lazy
抛出异常。使用标准:x5**2
方法,我不知道它是如何变得更简单。
它比Enumerable
方法慢。它基本上做同样的事情,应该具有相同的复杂性,但它确实使用了更多的方法调用并创建了更多的对象:
each
果味回报:
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
答案 1 :(得分:1)
查看stuff_in_trash.detect(&:eatable?)
的第一个示例,您可以执行以下操作:
stuff_in_trash.detect(&:eatable?)&.eatable?
请注意safe navigation operator(&.
)的使用,自红宝石v2.3+
开始提供,其中涵盖detect
返回nil
的边缘情况。< / p>
优点:
stuff_in_trash
列表,因为detect?
方法在第一个真实项目处停止。缺点:
eatable?
方法两次。 (可能的性能问题,通常是不好的做法。)detect
块中应用的方法更复杂。例如:make_artwork_from( items.detect {|item| make_item_from(item)}
- 这甚至没有涵盖detect
还原nil
的可能问题!查看make_artwork_from(item)
的第二个示例,您可以执行以下操作:
items.lazy.map {|item| make_artwork_from(item)}.detect(&:itself)
优点:
items
列表,因为 lazy 枚举器会查询“最小”项数以计算最终方法链的结果。make_artwork_from(item)
一次。缺点:
相当不言自明 - 你可以定义一个方法:
module Enumerable
def detect_result
self.detect do |i|
if result = yield(i)
return result
end
end
end
end
# Usage:
items.detect_result { |item| make_artwork_from(item) }
优点:
items
列表,因为扩展类方法过早return
。make_artwork_from(item)
一次。缺点:
...我的意思是,将Enumerable
对象作为方法参数传递,而不是在对象上调用方法。与上面类似,但实现如下:
def detect_result(enumerable)
enumerable.detect do |i|
if result = yield(i)
return result
end
end
end
# Usage:
detect_result(items) { |item| make_artwork_from(item) }
优点:
与上述相同。
缺点:
detect_result
,这可能会导致运行时错误!C++
,Java
,Rust
,Scala
,...)中,上述内容将不是问题。< / LI>
就我个人而言,我认为使用懒惰的枚举器是最优雅,通用的解决方案。但我想提供一些替代方案进行比较。
答案 2 :(得分:1)
array.inject(nil) {|c,v| c or m(v)}
答案 3 :(得分:0)
您可以使用所需的迭代器扩展Enumerable:
module Enumerable
def detect_return
self.detect do |i|
r = yield(i) and return r
end
end
end
[1, 2, 3].detect_return do |i|
if i + 1 >= 2
puts "I will be printed only once"
"ok, here I am"
end
end
# I will be printed only once
# => "ok, here I am"
就我们都认为猴子修补是一件坏事而言,让我们提供更少的伤害变体:
def detect_return(enumerable)
enumerable.detect do |i|
r = yield(i) and return r
end
end
detect_return([1, 2, 3]) do |i|
if i + 1 >= 2
puts "I will be printed only once"
"ok, here I am"
end
end
# I will be printed only once
# => "ok, here I am"
detect_return([1, 2, 3]) do |i|
if i + 1 >= 42
puts "I will be printed only once"
"ok, here I am"
end
end
# => nil
答案 4 :(得分:0)
您可以简单地使用break
:
# "pseudo" code
my_enum.find do |item|
calculated_result = begin
# some stuf
end
break calculated_result if my_condition_is_true
end
# Or from the given link exemple
first_image_url = posts.find do |post|
break post.image.url if post.image.present?
end