Is there a reason that we cannot iterate on "reverse Range" in ruby?

I tried to iterate backwards with using a Range and each:

(4..0).each do |i|
puts i
end
==> 4..0

Iteration through 0..4 writes the numbers. On the other Range r = 4..0 seems to be ok, r.first == 4, r.last == 0.

It seems to be strange to me that the construct above does not produce the expected result. What is the a reason for that? What are the situations when this behaviour is reasonable?

47561 次浏览

范围就是这样: 由它的开始和结束来定义,而不是由它的内容来定义。在一般情况下,在一个范围内进行“迭代”是没有意义的。例如,考虑如何在两个日期产生的范围内“迭代”。你会白天迭代吗?按月计算?年份?一个星期?不是很清楚。事实上,它的允许前进的范围应该被视为一个方便的方法只。

If you want to iterate backwards over a range like that, you can always use downto:

$ r = 10..6
=> 10..6


$ (r.first).downto(r.last).each { |i| puts i }
10
9
8
7
6

下面是其他人关于为什么同时允许迭代和持续处理反向范围很困难的 更多的想法

使用 each在 Ruby 中迭代一个范围,调用该范围中第一个对象的 succ方法。

$ 4.succ
=> 5

And 5 is outside the range.

You can simulate reverse iteration with this hack:

(-4..0).each { |n| puts n.abs }

约翰指出,如果它跨越0,那么它就不会工作。这将:

>> (-2..2).each { |n| puts -n }
2
1
0
-1
-2
=> -2..2

不能说我真的喜欢他们中的任何一个,因为他们有点模糊的意图。

如果名单不是那么大。 我想是的 [*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

根据《编程 Ruby 》一书,Range 对象存储范围的两个端点,并使用 .succ成员生成中间值。取决于您在您的范围内使用的数据类型,您总是可以创建 Integer的子类并重新定义 .succ成员,以便它像反向迭代器一样工作(您可能也想重新定义 .next)。

您也可以不使用 Range 实现所需的结果:

4.step(0, -1) do |i|
puts i
end

这将以 -1的步骤从4步骤步进到0步。但是,我不知道这是否适用于整数参数以外的任何参数。

正如 bta 所说,原因是 Range#eachsucc发送到它的开始,然后发送到该 succ调用的结果,依此类推,直到结果大于结束值。你不能通过调用 succ从4到0,事实上你已经开始比结束大。

你甚至可以使用 for循环:

for n in 4.downto(0) do
print n
end

印刷品:

4
3
2
1
0

How about (0..1).reverse_each which iterates the range backwards?

另一种方法是 (1..10).to_a.reverse

这对我的懒惰用例很有用

(-999999..0).lazy.map{|x| -x}.first(3)
#=> [999999, 999998, 999997]

《观察家报》写道

我觉得奇怪的是,上面的结构并没有产生 预期的结果。原因是什么 这种行为是否合理?

不是“能做到吗?”?而是回答一个在真正被问到之前没有被问到的问题:

$ irb
2.1.5 :001 > (0..4)
=> 0..4
2.1.5 :002 > (0..4).each { |i| puts i }
0
1
2
3
4
=> 0..4
2.1.5 :003 > (4..0).each { |i| puts i }
=> 4..0
2.1.5 :007 > (0..4).reverse_each { |i| puts i }
4
3
2
1
0
=> 0..4
2.1.5 :009 > 4.downto(0).each { |i| puts i }
4
3
2
1
0
=> 4

由于 return _ each 声称构建了一个完整的数组,因此 downto 显然会更有效率。语言设计人员甚至可以考虑实现这样的东西,这一事实与所提出的实际问题的答案有关。

为了回答这个问题。

原因在于 Ruby 是一种永无止境的令人惊讶的语言。有些惊喜是令人愉快的,但有许多行为是彻底破坏的。即使下面的一些例子被更新的版本修正了,还有很多其他的例子,它们仍然是对原始设计思维的控诉:

nil.to_s
.to_s
.inspect

结果是“但是”

nil.to_s
#  .to_s   # Don't want this one for now
.inspect

结果出来了

 syntax error, unexpected '.', expecting end-of-input
.inspect
^

您可能希望 < < 和 push 对于附加到数组来说是相同的,但是

a = []
a << *[:A, :B]    # is illegal but
a.push *[:A, :B]  # isn't.

您可能希望“ grep”的行为与 Unix 命令行等价,但是它确实与 not = ~ 相匹配,尽管它的名字不同。

$ echo foo | grep .
foo
$ ruby -le 'p ["foo"].grep(".")'
[]

各种各样的方法都是彼此意想不到的别名,因此您必须为同一个东西学习多个名称-例如 finddetect-即使您像大多数开发人员一样只使用其中一个或另一个。对于 sizecountlength,情况大致相同,除了类的定义各不相同,或者根本不定义一个或两个。

除非有人已经实现了其他东西——比如核心方法 tap已经在各种自动化库中重新定义,以便在屏幕上按下某些东西。祝你好运,找出正在发生的事情,特别是当某个模块需要其他模块的时候,另一个模块已经做了一些没有文档说明的事情。

环境变量对象 ENV 不支持“ merge”,所以你必须写

 ENV.to_h.merge('a': '1')

作为奖励,你甚至可以重新定义你或别人的常量,如果你改变了你的想法,他们应该是什么。

对我来说,最简单的方法是:

[*0..9].reverse

对枚举进行迭代的另一种方法是:

(1..3).reverse_each{|v| p v}