有没有一种优雅的方法来遍历数组并在结束前停止?
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
在这两种情况下,只要第一个元素匹配条件,迭代就会退出。
您有效地做的是,首先 map
ping the elements of the Array
with a transformation, and then find
ing 第一个 满足某些谓词的转换元素。
我们可以这样表达(我重新使用了 中的定义,但增加了副作用,以便您可以观察转换操作的评估):
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
我想编写执行此操作的代码(无论如何,这是一个更复杂的版本):
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
在这两种情况下,只要第一个元素匹配条件,迭代就会退出。
您有效地做的是,首先 map
ping the elements of the Array
with a transformation, and then find
ing 第一个 满足某些谓词的转换元素。
我们可以这样表达(我重新使用了
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