【问题标题】:Ruby - Array.find, but return the value the blockRuby - Array.find,但返回块的值
【发布时间】:2021-11-16 05:44:25
【问题描述】:

我有一个数组,我想要第一个块的结果,它返回一个真值(又名,不是 nil)。问题是,在我的实际用例中,测试有一个副作用(我实际上是在迭代一组队列,并从顶部弹出),所以我不需要评估第一次成功之后的块。

a,b,c = [1,2,3]

[a,b,c].first_but_value{ |i| (i + 1) == 2 } == 2

a == 2
b == 2
c == 3

有什么想法吗?

【问题讨论】:

  • @sevenseacat,请指出我哪里粗鲁,以便我改正。
  • 您使用的术语我不清楚。结果是什么”?返回值?还是数组元素?什么是“块的结果”?接下来,“truthy”后面的括号让我感到困惑,因为 false 也不是truthy / truey。接下来,“测试有副作用”。我不知道您所说的“测试”是什么意思。块评估?接下来,您“不需要在第一次成功之后评估块”。这不是#find 方法通常所做的吗?我已经写了一个答案,但我不相信你真的是你所要求的。
  • 最后,你的区块中的+= 运算符没有意义,因为i 在每次区块评估后都会被丢弃。
  • @sevenseacat,我用更正式的语气重写了我的 cmets。内容没有改变。现在音调可以吗?
  • 是的,== 是粗略的断言。而且我总是忘记 Ruby 的 += 并不总是按我期望的方式工作(从 C++ 时代开始),所以你是对的,我错误地使用了我想要实现的目标。在实际用例中,它是.pop,但我觉得这需要额外的背景知识。 “结果”肯定是块的返回值。我不知道任何其他方式来解释“结果”。在我的用例中,该块将评估为nil 而不是false,并不是每个阅读这些问题的人都会知道nil 是假的

标签: ruby


【解决方案1】:

break丑=P

如果你想要一个函数式的方法,你想要一个惰性映射:

[nil, 1, 2, 3].lazy.map{|i| i && i.to_s}.find &:itself   
# => "1"

如果您不相信它没有在整个数组中迭代,只需打印出来看看:

[nil, 1, 2, 3].lazy.map{|i| (p i) && i.to_s}.find  &:itself
# nil
# 1
# => "1"

用你的块替换i.to_s

【讨论】:

  • 错了,这就是lazy 存在的原因。使用lazy,您不必映射所有元素。我什至提供了一个带印刷的版本,所以如果你不相信,你可以自己测试。
  • 以前没有听说过lazy。谢谢!
【解决方案2】:
[1, 2, 3].detect { |i| i += 1; break i if i == 2 } 
# => 2

[1, 2, 3].detect { |i| i += 1; break i if i == 10 }
# => nil

【讨论】:

  • 很好,不知道你可以像这样向break 提供一个值(或者那会成为循环表达式的值)
  • 这个想法是,在 Ruby 中 breakreturn 本质上是相同的。一个用于循环,另一个用于函数上下文。通过打破循环,我们提前返回。如果没有 arg 与 break 我们将简单地返回 nil 但使用“返回值”我们可以获得所需的返回行为。 (同样适用于任何循环)
  • 小心这个,好像什么都没找到一样,返回值会是原来的数组:[1, 2, 3].each{|i| i += 1; break i if i == 10} => [1, 2, 3]
  • @CarsonReinke 简单修复,改用#detect
  • @Kache,是的,它一直都在那里,很奇怪。
【解决方案3】:

find_yield 做你想做的事,查看带有许多核心扩展的 ruby​​ 方面,尤其是 find_yield Enumberable 方法:https://github.com/rubyworks/facets/blob/master/lib/core/facets/enumerable/find_yield.rb

【讨论】:

  • 非常酷!如果/当它被拉入核心 ruby​​ 时重新弹出,所以我可以将其标记为答案。
【解决方案4】:

这是你想做的吗?

a, b, c = 1, 2, 3

binding.tap { |b|
  break b.local_variable_get [ :a, :b, :c ].find { |sym|
    b.local_variable_set( sym, b.local_variable_get( sym ) + 1 ) == 2
  }
} #=> 2

a #=> 2
b #=> 2
c #=> 3

【讨论】:

  • 啊,这很好。我不会想到使用绑定。学到了一些新东西。看?那不是很有趣吗?
  • 我从来没有遇到过binding,好整洁!我必须查一下才能理解你的答案,但谢谢!
  • @diego.greyrobot,我让 Matz 客观化变量 quite some time ago。看来现在我们至少让Binding 明确地知道它的局部变量符号。我还在等待= 钩子:-)
【解决方案5】:

这是我的看法,这更接近您的实际用例吗?注意b 的内容是3 而不是2,因为my_test_with_side_effect 也被b 调用。

class MyQueue
  def initialize(j)
    @j = j
  end  
  def my_test_with_side_effect
    (@j+=1) == 2
  end
end

(a,b,c) = [MyQueue.new(1),MyQueue.new(2),MyQueue.new(3)]
[a,b,c].each { |i| break i unless i.my_test_with_side_effect }
=> #<MyQueue:0x007f3a8c693598 @j=3>
a
=> #<MyQueue:0x007f3a8c693980 @j=2>
b
=> #<MyQueue:0x007f3a8c693598 @i=3>
c
=> #<MyQueue:0x007f3a8c693430 @i=3>

【讨论】:

    【解决方案6】:

    我怀疑有办法做到这一点。问题是 Ruby 在块中创建了一个闭包,而变量 i 是它的本地变量。执行i+=1 可以扩展为i = i + 1,这会在块的范围内创建一个新变量i,并且不会修改任何a,b,c 变量中的值。

    【讨论】:

    • 所以他要修改abc变量?你怎么知道?而且,顺便说一句,在 Ruby 中,几乎所有事情都可以做。但我需要知道提问者想要什么。
    • 看起来他们可能一直在做一个粗略的断言,他们在代码 sn-p 的下半部分写了a == 2
    • 他真的想通过块来修改局部变量吗?
    • 显然我是这么认为的。你为什么不自己问他?
    • @BorisStitnicky:这是我的实际用例。我有一系列生成的 Resque 队列,我想从它们的集合中获得第一份工作。所以,如果我使用上面的答案,我想要的是:job_data = queue_names.each{|q| job = Resque.pop(q); break job if job}。这是否告诉你我为什么要做这样的事情?
    【解决方案7】:

    类似于@ribamar's answer,你也可以交替使用lazyfilter_mapfirst

    [nil, 1, 2, 3].lazy.filter_map{|i| i && i.to_s}.first 
    # => "1"
    

    我发现它比find(&amp;:itself) 更容易阅读。

    注意:filter_map 是在 Ruby 2.7 中添加的,因此如果您需要支持旧版本的 Ruby,将无法使用。

    【讨论】:

      猜你喜欢
      • 2016-01-04
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-17
      相关资源
      最近更新 更多