有没有理由我们不能在ruby中反复“反向范围”?

时间:2010-01-15 09:36:34

标签: ruby iteration range

我尝试使用范围和each

向后迭代
(4..0).each do |i|
  puts i
end
==> 4..0

通过0..4进行迭代会写入数字。在另一个范围r = 4..0似乎没问题,r.first == 4r.last == 0

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

12 个答案:

答案 0 :(得分:89)

(0..1).reverse_each向后迭代范围怎么样?

答案 1 :(得分:88)

范围就是:由开始和结束定义的东西,而不是其内容。在一般情况下,在一个范围内“迭代”并不真正有意义。例如,考虑如何“迭代”两个日期产生的范围。你会白天迭代吗?按月?一年?一周?它没有明确定义。 IMO,允许前进范围这一事实应被视为一种便利方法。

如果你想在这样的范围内向后迭代,你可以随时使用downto

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

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

以下是来自其他人的some more thoughts,为什么很难同时允许迭代并始终​​处理反向范围。

答案 2 :(得分:18)

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

$ 4.succ
=> 5

并且5超出范围。

您可以使用此hack模拟反向迭代:

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

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

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

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

答案 3 :(得分:12)

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

您还可以在不使用范围的情况下获得所需的结果。试试这个:

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

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

答案 4 :(得分:10)

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

答案 5 :(得分:4)

您甚至可以使用for循环:

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

打印:

4
3
2
1
0

答案 6 :(得分:3)

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

答案 7 :(得分:1)

我添加另一种可能性如何实现反向范围的迭代。我不使用它,但这是一种可能性。猴子修补红宝石核心物体有点冒险。

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

答案 8 :(得分:1)

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

答案 9 :(得分:0)

这适用于我的懒惰用例

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

答案 10 :(得分:0)

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构建了一个完整的数组,因此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
 ^

您可能会期望&lt;&lt;和推送是相同的附加到数组,但

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')

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

答案 11 :(得分:0)

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

[*0..9].reverse

迭代枚举的另一种方法:

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