是否存在我们无法迭代“反向范围”的原因?在红宝石中?

发布于 2024-08-18 16:04:38 字数 287 浏览 5 评论 0原文

我尝试使用 Range 和 each 向后迭代:

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

爱人如己 2024-08-25 16:04:38

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

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

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

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

这里有 其他人的更多想法,解释为什么既允许迭代又一致处理反向范围很难。

A range is just that: something defined by its start and end, not by its contents. "Iterating" over a range doesn't really make sense in a general case. Consider, for example, how you would "iterate" over the range produced by two dates. Would you iterate by day? by month? by year? by week? It's not well-defined. IMO, the fact that it's allowed for forward ranges should be viewed as a convenience method only.

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

Here are some more thoughts from others on why it's tough to both allow iteration and consistently deal with reverse-ranges.

不交电费瞎发啥光 2024-08-25 16:04:38

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

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

冷夜 2024-08-25 16:04:38

在 Ruby 中使用 each 迭代范围会调用该范围中第一个对象的 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

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

Iterating over a range in Ruby with each calls the succ method on the first object in the range.

$ 4.succ
=> 5

And 5 is outside the range.

You can simulate reverse iteration with this hack:

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

John pointed out that this will not work if it spans 0. This would:

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

Can't say I really like any of them because they kind of obscure the intent.

多情癖 2024-08-25 16:04:38

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

Another way is (1..10).to_a.reverse

平安喜乐 2024-08-25 16:04:38

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

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

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

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

According to the book "Programming Ruby", the Range object stores the two endpoints of the range and uses the .succ member to generate the intermediate values. Depending on what kind of data type you are using in your range, you can always create a subclass of Integer and re-define the .succ member so that it acts like a reverse iterator (you would probably also want to re-define .next as well).

You can also achieve the results you are looking for without using a Range. Try this:

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

This will step from 4 to 0 in steps of -1. However, I don't know if this will work for anything except Integer arguments.

小瓶盖 2024-08-25 16:04:38

您甚至可以使用 for 循环:

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

打印:

4
3
2
1
0

You can even use a for loop:

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

which prints:

4
3
2
1
0
守不住的情 2024-08-25 16:04:38

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

if list is not that big.
i think
[*0..4].reverse.each { |i| puts i }
is simplest way.

稚气少女 2024-08-25 16:04:38

正如 bta 所说,原因是 Range#eachsucc 发送到其开头,然后发送到 succ 调用的结果,依此类推直到结果大于最终值。您无法通过调用 succ 从 4 到 0,事实上您的起点已经大于终点。

As bta said, the reason is that Range#each sends succ to its beginning, then to the result of that succ call, and so on until the result is greater than the end value. You can't get from 4 to 0 by calling succ, and in fact you already start out greater than the end.

注定孤独终老 2024-08-25 16:04:38

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

I add one another possibility how to realise iteration over reverse Range. I do not use it, but it is a possibility. It is a bit risky to monkey patch ruby core objects.

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
昨迟人 2024-08-25 16:04:38

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
 ^

您可能会期望 <<和 push 与追加到数组时相同,但

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

您可能会期望 'grep' 的行为类似于其 Unix 命令行等效项,但它确实 === 匹配而不是 =~,尽管它的名字如此。

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

各种方法意外地互为别名,因此您必须学习同一事物的多个名称 - 例如 finddetect - 即使您确实像大多数开发人员一样并且只使用其中之一。 sizecountlength 的情况大致相同,除了定义各自不同的类,或者没有定义一两个的类之外。全部。

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

环境变量对象 ENV 不支持“合并”,因此您必须编写

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

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

The OP wrote

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?

not 'Can it be done?' but to answer the question that wasn't asked before getting to the question that was actually asked:

$ 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

Since reverse_each is claimed to build an entire array, downto is clearly going to be more efficient. The fact that a language designer could even consider implementing things like that kinda ties into the answer to the actual question as asked.

To answer the question as actually asked...

The reason is because Ruby is an endlessly surprising language. Some surprises are pleasant, but there is a lot of behaviour which is downright broken. Even if some of these following examples are corrected by newer releases, there are plenty of others, and they remain as indictments on the mindset of the original design:

nil.to_s
   .to_s
   .inspect

results in "" but

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

results in

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

You would probably expect << and push to be the same for appending to arrays, but

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

You would probably expect 'grep' to behave like its Unix command-line equivalent, but it does === matching not =~, despite its name.

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

Various methods are unexpectedly aliases for each other, so you have to learn multiple names for the same thing - e.g. find and detect - even if you do like most developers and only ever use one or the other. Much the same goes for size, count, and length, except for classes which define each differently, or don't define one or two at all.

Unless someone has implemented something else - like the core method tap has been redefined in various automation libraries to press something on the screen. Good luck finding out what's going on, especially if some module required by some other module has monkeyed yet another module to do something undocumented.

The environment variable object, ENV does not support 'merge', so you have to write

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

As a bonus, you can even redefine your or someone else's constants if you change your mind about what they should be.

初懵 2024-08-25 16:04:38

非常老的问题,但今天我发现了以下方法(之前没有提到过):

(4..0).step(-1).each {|x| puts x} 

使用显式步骤允许 ruby​​“知道”如何继续到下一个值(而不是使用默认的 succ )。

这种方法的优点是不需要首先实例化完整的数组,而且它仍然具有相当的可读性/紧凑性。

Very old question, but today I found the following approach (which was not mentioned before):

(4..0).step(-1).each {|x| puts x} 

Using an explicit step allows ruby to "know" how to proceed to the next value (instead of using the default succ).

The advantage of this approach is that no complete array has to be instantiated first, and it still is pretty readable/compact imho.

祁梦 2024-08-25 16:04:38

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

[*0..9].reverse

另一种迭代枚举的方法:

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

As for me the simplest way is:

[*0..9].reverse

Another way to iterate for enumeration:

(1..3).reverse_each{|v| p v}
乄_柒ぐ汐 2024-08-25 16:04:38

这适用于我的懒惰用例

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

This worked for my lazy use case

(-999999..0).lazy.map{|x| -x}.first(3)
#=> [999999, 999998, 999997]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文