ルビーで「リバースレンジ」を反復できない理由はありますか?


104

私はRangeを使用して逆方向に反復しようとしましたeach

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

反復0..4は数値を書き込みます。他のレンジにr = 4..0、OKのようですr.first == 4r.last == 0

上記の構成が期待した結果を生成しないのは私には奇妙に思われます。その理由は何ですか?この動作が合理的である状況は何ですか?


明らかにサポートされていないこの反復を実現する方法だけでなく、なぜ範囲4..0自体を返すのかにも興味があります。言語デザイナーの意図は何でしたか?なぜ、どのような状況でそれが良いのですか?他のルビー構造でも同様の動作が見られましたが、有用な場合はまだクリーンではありません。
fifigyuri、2010年

1
範囲自体は慣例により返されます。以来.each文は何も変更しなかった、ノーリターンに「結果」が計算されません。この場合、Rubyは通常、成功時とnilエラー時に元のオブジェクトを返します。これにより、このような式をifステートメントの条件として使用できます。
bta

回答:


99

範囲とは、その内容ではなく、開始と終了によって定義されるものです。ある範囲で「繰り返す」ことは、一般的なケースでは実際には意味がありません。たとえば、2つの日付によって生成された範囲をどのように「反復」するかを考えます。日ごとに繰り返しますか?月ごと?年によって?週ごと?それは明確に定義されていません。IMO、それが前方範囲に許可されているという事実は、便利な方法としてのみ見なされるべきです。

そのような範囲で逆方向に反復したい場合は、いつでも使用できますdownto

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

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

ここでは、反復を許可し、一貫して逆の範囲を処理することの両方が難しい理由について、他の人からのいくつかの考えを示します。


10
1から100または100から1の範囲で反復することは、ステップ1を使用することを意味すると思います。別のステップが必要な場合は、デフォルトを変更してください。同様に、私にとって(少なくとも)1月1日から8月16日まで繰り返すとは、日ごとにステップ実行することを意味します。直感的にそういう意味で言っているので、よく合意できることがあると思います。あなたの答えをありがとう、あなたが与えたリンクも役に立ちました。
fifigyuri、

3
多くの範囲で「直感的な」反復を定義することは一貫して行うのが難しいと思います。このように日付を反復すると直感的に1日に相当するステップを意味することに同意しません。時間(真夜中から真夜中まで)。たとえば、「1月1日から8月18日」(正確には20週間)は、日ではなく週の反復を意味しないと誰が言っているのでしょうか。時間、分、秒ごとに反復しないのはなぜですか?
John Feminella、2010年

8
.each冗長であり、5.downto(1) { |n| puts n }罰金を動作します。また、すべてのr.first r.lastの代わりに、を実行してください(6..10).reverse_each
mk12 '30 / 07/30

@ Mk12:100%同意します。私は、新しいRubyistのために超明示的になることを試みていました。多分それはあまりにも混乱しています。
John Feminella、2014年

年をフォームに追加するときに、私は次を使用しました:= f.select :model_year, (Time.zone.now.year + 1).downto(Time.zone.now.year - 100).to_a
Eric Norcross


18

Rubyの範囲を反復処理して、範囲の最初のオブジェクトのメソッドをeach呼び出しsuccます。

$ 4.succ
=> 5

そして5は範囲外です。

このハックで逆反復をシミュレートできます:

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

Johnは、これが0にまたがる場合は機能しないことを指摘しました。

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

彼らが意図を曖昧にしているので、私はそれらのどれも本当に好きだとは言えません。


2
いいえ。ただし、.absを使用する代わりに-1を掛ければできます。
JonasElfström、2010年

12

本「プログラミングRuby」によれば、Rangeオブジェクトは範囲の2つのエンドポイントを格納し、.succメンバーを使用して中間値を生成します。範囲内で使用しているデータ型の種類に応じて、常にメンバーのサブクラスを作成しInteger.succメンバーを再定義し、リバースイテレーターのように機能させることができます(おそらく再定義する.nextこともできます)。

Rangeを使用しなくても、探している結果を得ることができます。これを試して:

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

これは、-1のステップで4から0にステップします。ただし、これが整数引数以外の場合に機能するかどうかはわかりません。



5

forループを使用することもできます。

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

印刷する:

4
3
2
1
0

3

リストがそれほど大きくない場合。私[*0..4].reverse.each { |i| puts i } は最も簡単な方法だと思います 。


2
IMOそれが大きいと仮定することは一般的に良いことです。一般的に従うのは正しい信念と習慣だと思います。そして、悪魔は決して眠らないので、私は自分がアレイを反復した場所を覚えていると自分を信じていません。しかし、あなたが正しい、0と4の定数がある場合、配列を反復しても問題は発生しない可能性があります。
fifigyuri、2010年

1

btaが言ったように、その理由は、最初にRange#each送信succし、次にそのsucc呼び出しの結果に送信することであり、結果が最終値よりも大きくなるまで続きます。を呼び出すことで4から0に到達することはできずsucc、実際にはすでに終了よりも大きく開始しています。


1

逆の範囲で反復を実現する方法をもう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

0

これは私の怠惰なユースケースでうまくいきました

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

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
 ^

おそらく、<<と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それぞれ異なるを定義する、またはすべてで1または2を定義していないクラスを除き、。

誰かが何か他のものを実装していない限り-コアメソッドのtapように、画面上で何かを押すためにさまざまなオートメーションライブラリで再定義されています。特に他のモジュールが必要とするいくつかのモジュールが、文書化されていない何かをするためにさらに別のモジュールを呼び出している場合は特に、何が起こっているかを見つけるのに頑張ってください。

環境変数オブジェクトであるENVは「マージ」をサポートしていないため、次のように記述する必要があります。

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

おまけとして、自分や他の人の定数を再定義することもできます。


これは、どのような形、形、または形式の質問にも答えません。それは作者がRubyについて好きではないことについての怒りに他なりません。
イェルクWミッターク

実際に尋ねられた答えに加えて、尋ねられなかった質問に答えるように更新されました。暴言:動詞1.怒ったり、情熱的な方法で長々と話したり叫んだりします。元の答えは怒ったり熱烈なものではありませんでした。
android.weasel 2017

@JörgWMittag元の質問にも含まれています:上記の構成が期待した結果を生成しないことは私には奇妙に思われます。その理由は何ですか?この動作が合理的である状況は何ですか?だから彼は理由の後で、コードの解決策ではありません。
android.weasel 2017

grep繰り返しますが、空の範囲を反復することは何もしないという事実に関連して、の動作がどのように、形や形になっているかはわかりません。また、空の範囲を反復処理することが何の効果もないという事実が、「途方もなく驚くべき」および「まったく壊れている」という形や形であることもわかりません。
イェルクWミッターク

範囲4..0には[4、3、2、1、0]の明確な意図がありますが、驚くべきことに警告も発生しません。それはOPを驚かせ、私を驚かせ、そして疑いなく他の多くの人々を驚かせました。意外な行動の他の例を挙げました。よろしければ、さらに引用できます。何かが一定以上の驚くべき行動を示すと、それは「壊れた」領域に流れ始めます。定数が上書きされたとき、特にメソッドが上書きされないときに警告が発生する方法に少し似ています。
android.weasel 2017

0

私にとって最も簡単な方法は:

[*0..9].reverse

列挙を繰り返す別の方法:

(1..3).reverse_each{|v| p v}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.