在Ruby中提前跳出.each{}迭代

43

代码:

 c = 0  
 items.each { |i|  
   puts i.to_s    
   # if c > 9 escape the each iteration early - and do not repeat  
   c++  
 }  
我想抓取前10个项目,然后离开"each"循环。
我应该用什么替换注释行?有没有更好的方法?更符合Ruby惯用语法的方法?

我建议使用nimrodm的答案,他使用了take函数:https://dev59.com/iHI_5IYBdhLWcg3wAeHl#1568445 - Robert K
8个回答

62

虽然 break 方案可行,但我认为一种更加功能化的方法更适合这个问题。你想要取出前十个元素并打印它们,所以尝试使用

items.take(10).each { |i| puts i.to_s }

15
简短版:puts items.take(10) - Telemachus
我刚刚尝试在谷歌上搜索“ruby take method”,但没有找到关于take模块的参考资料。它在API中的哪个模块里? - Sarah Vessels
1
我想我需要升级我的Ruby版本:关于Array#take什么也不知道 - Chris Lutz
1
Array#take 存在于 Ruby 1.8.7 及以上版本。 - Chuck
啊啊啊。我喜欢函数式编程。"你们都认识我!" +1 并回答。我比之前的答案更喜欢这个。但知道有 break 选项也很好。在测试期间注释掉 break 行很容易。 - BuddyJoe
显示剩余2条评论

55

Ruby 中没有 ++ 运算符。另外,惯例是在多行代码块中使用 doend。修改你的解决方案如下:

c = 0  
items.each do |i|  
  puts i.to_s    
  break if c > 9
  c += 1 
end

或者也可以这样:

items.each_with_index do |i, c|  
  puts i.to_s    
  break if c > 9
end

参见each_with_index以及Programming Ruby Break, Redo, and Next

更新:使用范围的Chuck的答案更符合Ruby的习惯,而使用takenimrodm的答案则更好。


谢谢。回答并给个赞。哇,我的初始语法完全错误。 - BuddyJoe
你的答案并没有离谱:唯一无效的部分是 ++。花括号用于块是可以的,只是对于多行块不是首选;请参见 https://dev59.com/enRB5IYBdhLWcg3wuZfo - Sarah Vessels
我喜欢你的第一个解决方案,因为如果你想每次循环100个项目,但只有在特定条件下取出10个,你可以独立地增加计数器。 - chrisallick

8

break 可以用来提前退出循环,但更加惯用的方法是使用 items[0..9].each {|i| puts i}。(如果你只是简单地打印项目而没有做任何修改,可以直接使用 puts items[0..9]。)


1
我会将其编写为:puts items[0..9].join("\n") - Bob Aman

4

另一种变体:

puts items.first(10)

请注意,这适用于少于10个项的数组:
>> nums = (1..5).to_a
=> [1, 2, 3, 4, 5]
>> puts nums.first(10)
1
2
3
4
5

(另外需要注意的是,很多人会提供类似于puts i.to_s的形式,但在这种情况下,.to_s是否有些多余呢?我认为puts会自动调用非字符串类型的.to_s来打印输出。只有当您想要说puts 'A' + i.to_s或类似内容时,才需要使用.to_s。)

4
另一个选择是:
items.first(10).each do |i|
  puts i.to_s
end

对我来说,这比在迭代器上中断要容易理解得多,如果没有足够的项,"first"仅返回可用项数。


1

这看起来像是你想要的吗?

10.times { |i|
  puts items[i].to_s
}

那样做是可行的,但我不能保证源数据始终至少有10个项目。 - BuddyJoe
еХКгАВдљ†еПѓдї•жЈїеК†break if items[i] == nilпЉМдљЖж≠§жЧґзЬЛиµЈжЭ•еЇФиѓ•дљњзФ®each_with_indexгАВ - Chris Lutz

0
items.each_with_index { |i, c| puts i and break if c <= 9 }

那将在第一项后中断。 - Chuck
不是很确定,你是怎么测试的? - khelll
1
@Khelll:这是由于and的惰性求值吗?它可以工作,但对我来说有点太聪明了。因为我看到“and break if”在一起,我的大脑一直想着使用>= - Telemachus
好的,现在我明白了:当puts工作时,它返回nil。因此,只要索引等于或小于9,就会发生puts,返回nil并且不会评估and的第二个部分。当索引达到10时,puts不会发生,and的第二个部分被评估。此时,出现问题:break - Telemachus
3
之前我理解错了,但还是错的:这个东西 永远不会 中断。可以这样看待它:if c <= 9; puts i; break; endand break 永远不会被执行,因为 puts i 总是返回 nil,一旦 c>9,整个 if 语句块就不再执行。如果想证明这个分支永远不会到达,请将 break 替换为 (puts "YOU WON'T SEE THIS") - Chuck
1
@Chuck:谢谢再来一轮。@Khelll:我认为我们已经证明它读起来不太自然。 - Telemachus

-2
被问到:
“我想获取前10个项目,然后离开“each”循环。”
使用throwcatch来完成这个任务,对示例进行少量更改:
catch(:done) do
    c = 0
    collected = []
    items.each do |item|
        collected << item
        throw(:done, collected) if c == 9 # started at 0
        c += 1
    end
    collected # if the list is less than 10 long, return what was collected
end

只需使用throw指令并带上标签:donecollected参数,等待:done标签的catch指令将返回collected参数。

如果要使用Ruby语言实现:

catch(:done) do
    items.inject([]) do |collected, item|
        throw(:done, collected) if collected.size == 10
        collected << item # collected gets returned here and populates the first argument of this block
    end
end

我不知道为什么有些人拒绝使用inject而使用reduce(它们是等效的),当明显给inject([])中传入了item时,空数组被注入!总之,如果少于10项,inject将返回collected

大多数答案试图回答问题的意图而不是被问到的内容,在那种情况下items.take(10)确实是很有意义的。但我可以想象想要抓住适合100美元预算的第一批商品。然后你可以简单地:

catch(:done) do
    items.inject({items: [], budget: 100}) do |ledger, item|
        remainder = ledger[:budget] - item.price
        if remainder < 0
            throw(:done, ledger)
        else
            ledger.tap do |this|
                this[:items] << item
                this[:budget] = remainder
            end # tap just returns what is being tapped into, in this case, ledger
        end
    end
end

你把一个简单的问题回答得非常复杂。这里不需要使用 throwcatch,也不需要将其转化为13行深度嵌套的代码。保持简单。 - NeuroXc
我的答案有6行,展示了一种逃离each循环的替代方式,它比大多数答案嵌套深了一个级别。我留下这个答案的希望是展示如何利用这个简单的上下文来替代地退出循环。如果你真的读了我的帖子,我的13行代码是对我在答案中提出的更复杂的例子的更复杂的答案。对于这个回复中的太多单词,我提前道歉。 - Nothus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接