web-dev-qa-db-ja.com

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

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

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

0..4を反復処理すると、数値が書き込まれます。他の範囲r = 4..0は問題ないようです、r.first == 4r.last == 0

上記のコンストラクトが予期した結果を生成しないことは、私には奇妙に思えます。その理由は何ですか?この動作が合理的である状況は何ですか?

96
fifigyuri

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

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

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

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

他の人からの、より多くの考え は、反復を許可し、逆方向範囲を一貫して処理するのが難しい理由についてです。

90
John Feminella

(0..1).reverse_each範囲を逆方向に繰り返しますか?

90
Marko Taponen

Rubyで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

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

18
Jonas Elfström

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

範囲を使用せずに、探している結果を達成することもできます。これを試して:

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

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

12
bta

別の方法は(1..10).to_a.reverse

10
Sergey Kishenin

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

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

印刷するもの:

4
3
2
1
0
4
bakerstreet221b

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

3
marocchino

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

1
Chuck

逆範囲での反復を実現する方法をもう1つ追加します。使用していませんが、可能性があります。モンキーパッチ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
1
fifigyuri

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(".")'
[]

さまざまなメソッドは予想外に互いのエイリアスであるため、同じことに対して複数の名前を覚える必要があります。 findおよびdetect-ほとんどの開発者が好きで、どちらか一方のみを使用する場合でも。 sizecount、およびlengthについてもほぼ同じです。ただし、それぞれ異なる定義を行うクラス、または1つまたは2つをまったく定義しないクラスを除きます。

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

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

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

ボーナスとして、あなたが彼らがどうあるべきかについて考えを変えるなら、あなたまたはあなたの誰かの定数を再定義することさえできます。

0
android.weasel

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

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