代码之家  ›  专栏  ›  技术社区  ›  fifigyuri

我们不能在Ruby中的“反向范围”上迭代有什么原因吗?

  •  93
  • fifigyuri  · 技术社区  · 15 年前

    我尝试使用范围和 each :

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

    迭代 0..4 写下数字。在另一个范围 r = 4..0 似乎没问题, r.first == 4 , r.last == 0 .

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

    11 回复  |  直到 8 年前
        1
  •  87
  •   John Feminella    15 年前

    一个范围就是:由它的开始和结束定义的东西,而不是它的内容。”在一般情况下,“在一个范围内迭代”是没有意义的。例如,考虑如何在两个日期产生的范围内“迭代”。你会按天迭代吗?按月计算?按年计算?一周?它的定义不明确。在国际海事组织,这一事实,它允许向前的范围应被视为一个方便的方法只。

    如果要在这样的范围内向后迭代,则可以始终使用 downto :

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

    这里是 some more thoughts 从其他人的角度来解释为什么同时允许迭代和一致地处理反向范围是困难的。

        2
  •  89
  •   Marko Taponen    11 年前

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

        3
  •  18
  •   tig Charlie Martin    10 年前

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

    $ 4.succ
    => 5
    

    5超出范围。

    您可以使用这个hack来模拟逆向迭代:

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

    约翰指出,如果跨度为0,这将不起作用。这将:

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

    不能说我真的喜欢它们,因为它们有点模糊了我的意图。

        4
  •  12
  •   bta    15 年前

    根据“编程Ruby”一书,range对象存储范围的两个端点,并使用 .succ 成员以生成中间值。根据您在区域中使用的数据类型,可以始终创建 Integer 重新定义 SUCC 成员,以便它的行为类似于反向迭代器(您可能还需要重新定义 .next 同样如此。

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

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

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

        5
  •  10
  •   Sergey K    11 年前

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

        6
  •  4
  •   the Tin Man    9 年前

    你甚至可以使用 for 循环:

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

    哪些印刷品:

    4
    3
    2
    1
    0
    
        7
  •  3
  •   marocchino    15 年前

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

        8
  •  1
  •   Chuck    15 年前

    正如BTA所说,原因是 Range#each 发送 succ 从一开始,然后到结果 苏克 调用,依此类推,直到结果大于结束值。打电话不能从4到0 苏克 实际上,你已经开始的比结束的更重要了。

        9
  •  1
  •   fifigyuri    12 年前

    我添加了另一个可能性,如何实现反向范围内的迭代。我不使用它,但它是一种可能性。猴子修补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
    
        10
  •  0
  •   forforf    9 年前

    这适用于我懒惰的用例

    (-999999..0).lazy.map{|x| -x}.first(3)
    #=> [999999, 999998, 999997]
    
        11
  •  0
  •   android.weasel    8 年前

    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
    

    由于反向_每个都声称构建了一个完整的数组,因此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命令行等效,但它确实是==matching not=~,尽管它的名称是。

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

    各种方法都是彼此的意外别名,因此您必须为同一件事学习多个名称-例如。 find detect -即使你喜欢大多数开发人员,而且只使用其中一个。同样的道理 size , count length ,除了定义不同的类,或者根本不定义一个或两个。

    除非有人实现了其他东西——比如核心方法 tap 已在各种自动化库中重新定义,以便在屏幕上按某些内容。祝你好运,了解正在发生的事情,特别是如果其他模块所需的某个模块调用了另一个模块来执行未经记录的操作。

    环境变量对象env不支持“merge”,因此必须写入

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

    作为一个额外的好处,你甚至可以重新定义你或其他人的常量,如果你改变你对它们应该是什么的想法。