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

时间:2019-09-06 12:57:18

标签: ruby idioms

我想编写用于执行此操作的代码(嗯,无论如何,要复杂得多):

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

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

理想情况下,我想要Array上的某个方法,该方法可以让我依次测试每个元素,并返回答案,并在有答案时停止。 有这种方法吗?

我能找到的最接近的方法是在break中使用#inject。但是我基本上仍然是手工进行迭代!

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

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

更新:发言太早了!我需要惰性运算符!谢谢大家。

4 个答案:

答案 0 :(得分:2)

我想这就是你想要的:

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

我从here那里获取了这段代码

答案 1 :(得分:2)

您实际上正在做的是,首先mapping进行转换的Array元素,然后finding满足某些条件的 first 转换元素谓词。

我们可以这样表示(我正在重复使用@iGian's answer中的定义,但是有一个附加的副作用,以便您可以观察到转换操作的评估结果):

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。好吧,这很容易,我们只需要首先将Array转换为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?)

答案 2 :(得分:2)

您想要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

答案 3 :(得分:0)

其他两种选择,只有在您的代码可行的情况下,才能进行以下简化:

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

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