有没有一种优雅的方法来遍历数组并在结束前停止?

Is there an elegant way to iterate through an array and stop before the end?

我想编写执行此操作的代码(无论如何,这是一个更复杂的版本):

answer = nil
array.each do |e|
  x = complex_stuff_with(e)
  if x.is_the_one_i_want?
    answer = x
    break
  end
end

这显然不能令人满意。像这样遍历数组是一种代码味道。

理想情况下,我想要一些 Array 上的方法,让我依次测试每个元素,return 我的答案并在我有一个时停止。 有这样的方法吗?

我最接近的方法是在 #inject 中使用 break。但我基本上还是手工迭代!

answer = array.inject do |_,e|
  x = complex_stuff_with(e)
  break x if x.is_the_one_i_want?
end

更新:看来我找不到这样的方法是因为不存在这样的方法。

更新:说得太快了!我需要惰性运算符!谢谢大家。

我想这就是你想要的:

answer = array.find do |e|
  x = complex_stuff_with(e)
  break x if x.is_the_one_i_want?
end

我从 here

中获取了这段代码

其他几个选项,前提是您的代码可以进行以下简化:

def complex_stuff_with(e)
  e**2
end

ary = [1, 2, 3, 2] # integer objects
x_i_want = 4 # instead of x.is_the_one_i_want?

那么,在那种情况下:

res = ary.find { |e| complex_stuff_with(e) == x_i_want}.then { |e| e ? x_i_want : :whatever }
#=> 4

或使用Enumerable#any?:

res = x_i_want if ary.any? { |e| complex_stuff_with(e) == x_i_want }
#=> 4

在这两种情况下,只要第一个元素匹配条件,迭代就会退出。

您有效地做的是,首先 mapping the elements of the Array with a transformation, and then finding 第一个 满足某些谓词的转换元素。

我们可以这样表达(我重新使用了 中的定义,但增加了副作用,以便您可以观察转换操作的评估):

def complex_stuff_with(e)
  p "#{__callee__}(#{e.inspect})"
  e**2
end

ary = [1, 2, 3, 2]
x_i_want = 4

ary
  .map(&method(:complex_stuff_with))
  .find(&x_i_want.method(:==))
# "complex_stuff_with(1)"
# "complex_stuff_with(2)"
# "complex_stuff_with(3)"
# "complex_stuff_with(2)"
#=> 4

这给了我们正确的结果,但没有正确的副作用。你希望操作先lazy. Well, that's easy, we just need to convert the Array to a lazy enumerator

ary
  .lazy
  .map(&method(:complex_stuff_with))
  .find(&x_i_want.method(:==))
# "complex_stuff_with(1)"
# "complex_stuff_with(2)"
#=> 4

或者,使用您问题中的定义:

array
  .lazy
  .map(&method(:complex_stuff_with))
  .find(&:is_the_one_i_want?)

你想要 Lazy Enumerator!

array.lazy.map {|e| expensive_stuff(e) }.detect(&:is_the_one_i_want?)

这使得 Ruby 一次评估每个元素的整个操作链(映射、检测),而不是先评估所有元素的映射,然后再评估检测等。这样您就可以做到地图中复杂而昂贵的东西,而无需在整个可枚举的范围内进行计算。

举例说明:

expensive_stuff = ->(x) { puts "Doing expensive stuff with #{x}" ; x }
result = (1..Float::INFINITY).lazy.map {|e| expensive_stuff[e] }.detect(&:even?)
puts "Result: #{result}"

# Doing expensive stuff with 1
# Doing expensive stuff with 2
# Result: 2