是否有一个原因,我们不能迭代在ruby中的“反向范围”?

我尝试使用一个范围向后迭代, each

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

通过0..4迭代写入数字。 在另一个范围r = 4..0似乎是好的, r.first == 4r.last == 0

我似乎很奇怪,上面的结构没有产生预期的结果。 这是什么原因? 这种行为合理的情况是什么?

一个范围就是:它的开始和结束,而不是其内容。 在一般情况下,“遍历”范围并不合理。 例如,考虑如何在两个date产生的范围内“迭代”。 你会重复一天吗? 按月份? 年份? 按周? 这不是很明确。 国际海事组织,它允许前进的范围应该被视为一种便利的方法。

如果你想在这样的范围内迭代,你总是可以使用downto

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

这里有一些更多的想法 ,为什么它很难让迭代和一贯处​​理反向范围。

如何(0..1).reverse_each向后迭代范围?

在Ruby中遍历一个范围, each范围内的第一个对象each调用succ方法。

 $ 4.succ => 5 

而5在范围之外。

你可以用这个黑客模拟反向迭代:

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

约翰指出,如果它跨越0,这将不起作用。这将会:

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

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

根据“编程Ruby”一书,Range对象存储范围的两个端点,并使用.succ成员生成中间值。 根据您在范围内使用的数据types,您可以始终创buildInteger的子类,并重新定义.succ成员,使其像反向迭代器一样工作(您可能还需要重新定义.next以及)。

您也可以在不使用范围的情况下获得您正在查找的结果。 尝试这个:

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

这将以-1为步长从4步到0。 但是,我不知道这是否适用于除Integer参数以外的任何内容。

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

如果列表不是那么大。 我认为[*0..4].reverse.each { |i| puts i } [*0..4].reverse.each { |i| puts i }是最简单的方法。

你甚至可以使用for循环:

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

打印:

 4 3 2 1 0 

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

我添加另一种可能性如何实现反向范围的迭代。 我不使用它,但它是一种可能性。 猴子补丁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 

这对我懒惰的用例工作

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

OP写道

我似乎很奇怪,上面的结构没有产生预期的结果。 这是什么原因? 这种行为合理的情况是什么?

不是“能做到吗?” 但是在回答实际问到的问题之前,回答了没有被问到的问题:

 $ 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 

由于reverse_each声称构build了一个完整的数组,downto显然会更有效率。 语言devise者甚至可以考虑实现类似这样的事情,将问题与实际问题的答案联系起来。

为了回答这个问题

原因是因为Ruby是一个无限惊人的语言。 有些惊喜是令人愉快的,但是有很多行为被彻底打破。 即使下面的一些例子被更新的版本纠正,也有很多其他的例子,并且仍然是对原始devise思维的控制:

 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 ^ 

你可能会期望<<和追加到数组相同,但是

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

你可能会希望'grep'的行为与它的Unix命令行相同,但它的确如此===匹配not =〜,尽pipe它的名字。

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

各种各样的方法都是意外的别名,所以你必须为同样的事情学习多个名字 – 比如finddetect – 即使你像大多数开发人员那样做,而且只使用其中一个。 sizecountlength ,除了定义每个类别的类别,或者根本不定义一个或两个类别。

除非有人实施了其他的东西 – 比如核心方法,在各种自动化库中重新定义了tap屏幕上的东西。 祝你好运,找出发生了什么事情,特别是如果某些其他模块需要某个模块已经猴子另一个模块做一些无证的东西。

环境variables对象ENV不支持“合并”,所以你必须写

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

作为奖励,如果你改变主意,你甚至可以重新定义你的或他人的常量。