我尝试使用 Range 和 each
反向迭代:
(4..0).each do |i|
puts i
end
==> 4..0
迭代 0..4
会输出数字。而另一种范围 r = 4..0
看起来也没什么问题,r.first == 4
,r.last == 0
。
我认为上面的语法结构不产生预期结果很奇怪。这是什么原因?什么情况下这种行为是合理的?
一个范围(range)就是这样的:由其起始和结束定义,而不是由其内容定义。在一般情况下,“迭代”范围并没有什么意义。例如,考虑一下如何“迭代”两个日期之间的范围。你会按天、按月、按年、按周迭代吗?这并没有明确定义。在我看来,仅允许正向范围进行迭代应该被视为一种方便的方法。
如果你想反向迭代这样的范围,你可以使用 downto
:
$ r = 10..6
=> 10..6
$ (r.first).downto(r.last).each { |i| puts i }
10
9
8
7
6
以下是其他人对于为何同时允许迭代和处理反向区间会很困难的一些思考。
.each
是多余的,5.downto(1) {|n| puts n}
就可以了。另外,不需要用 r.first r.last
这样的代码,只需要使用 (6..10).reverse_each
即可。 - mk12= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
- Eric Norcross对于(0..1).reverse_each
,它会倒序迭代范围。
(Date.today.beginning_of_year..Date.today.yesterday).reverse_each
。谢谢。 - daniel在Ruby中使用each
迭代一个范围时,会调用该范围中第一个对象的succ
方法。
$ 4.succ
=> 5
而且5超出了范围。
您可以使用此技巧模拟反向迭代:
(-4..0).each { |n| puts n.abs }
John指出,如果跨越0,这种方法将无法奏效。这将会:
>> (-2..2).each { |n| puts -n }
2
1
0
-1
-2
=> -2..2
我不能说我真的喜欢它们中的任何一个,因为它们有点模糊了意图。
另一种方法是 (1..10).to_a.reverse
(10..1).to_a.reverse
不起作用。 - Blake Gearin根据《Programming Ruby》一书,Range对象存储范围的两个端点,并使用.succ
成员生成中间值。根据您在范围中使用的数据类型,您可以始终创建Integer
子类并重新定义.succ
成员,以使其像反向迭代器一样运作(您可能还想重新定义.next
)。
您也可以在不使用Range的情况下实现所需的结果。请尝试以下操作:
4.step(0, -1) do |i|
puts i
end
这将以-1的步长从4到0进行步进。但是,我不知道除了整数参数之外是否适用于其他任何类型的参数。
你甚至可以使用 for
循环:
for n in 4.downto(0) do
print n
end
打印以下内容:
4
3
2
1
0
[*0..4].reverse.each { |i| puts i }
。我添加了另一种实现反向范围迭代的可能性。虽然我没有使用它,但这是一种可能性。猴子补丁 Ruby 核心对象有点冒险。
class Range
def each(&block)
direction = (first<=last ? 1 : -1)
i = first
not_reached_the_end = if first<=last
lambda {|i| i<=last}
else
lambda {|i| i>=last}
end
while not_reached_the_end.call(i)
yield i
i += direction
end
end
end
Range#each
会将succ
发送给其起始值,然后发送给succ
调用的结果,依此类推,直到结果大于结束值。你无法通过调用succ
从4到0,实际上你的起始值已经大于结束值。(4..0).step(-1).each {|x| puts x}
.each
语句没有修改任何内容,因此没有计算出的“结果”可返回。在这种情况下,Ruby通常在成功时返回原始对象,在错误时返回nil
。这使您可以将这样的表达式用作if
语句的条件。 - bta