web-dev-qa-db-ja.com

Rubyで配列を反復処理する「正しい」方法は何ですか?

PHPは、そのすべてのいぼのために、この点でかなり良いです。配列とハッシュの間に違いはありません(たぶん私は素朴ですが、これは明らかに私には正しいようです)。

foreach (array/hash as $key => $value)

Rubyには、このようなことをするためのたくさんの方法があります。

array.length.times do |i|
end

array.each

array.each_index

for i in array

私はいつも使うのでハッシュはもっと理にかなっている

hash.each do |key, value|

配列に対してこれができないのはなぜですか?ひとつの方法だけを覚えておきたいのであれば、each_indexを使うことができると思いますが(それはインデックスと値の両方を利用可能にするためです)、ただvalueではなくarray[index]をしなければならないのは面倒です。


そうです、私はarray.each_with_indexを忘れていました。しかし、これは|value, key|に行き、hash.each|key, value|に行くので、吸います!これは異常ではないですか?

314
Tom Lehman

これはすべての要素を反復処理します。

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

プリント:

1
2
3
4
5
6

これはすべての要素を繰り返し処理し、値とインデックスを得ます。

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

プリント:

A => 0
B => 1
C => 2

あなたの質問から、どれを探しているのかよくわかりません。

519
Robert Gamble

right wayは誰もいないと思います。繰り返す方法はたくさんあり、それぞれ独自のニッチがあります。

  • 私はインデックスを気にしないので、eachは多くの用途に十分です。
  • each_ with _indexはそれぞれハッシュ#のように振舞います - あなたは値とインデックスを得ます。
  • each_index - インデックスだけ。私はこれをあまり使いません。 "length×"に相当します。
  • mapは反復するもう一つの方法で、ある配列を別の配列に変換したいときに便利です。
  • selectは、サブセットを選択したいときに使用する反復子です。
  • injectは、合計や積を生成したり、単一の結果を集めるのに役立ちます。

覚えておくべきことのように思えるかもしれませんが、心配しないでください、あなたはそれらのすべてを知らなくても通り過ぎることができます。しかし、さまざまな方法を学び、使用するようになるにつれて、コードはより明確で明確になり、Rubyを習得することができます。

75
AShelly

Array - > |value,index|およびHash - > |key,value|が正当ではないとは言っていませんが(Horace Loebのコメントを参照)、私はこの取り決めを期待するための正しい方法があると言っています。

配列を扱うときは、配列内の要素に注目します(インデックスは一時的なので、インデックスではありません)。メソッドはそれぞれインデックス付き、つまり各+インデックス、または| each、index |、または|value,index|です。これは、インデックスがオプションの引数と見なされていることとも一致しています。 |値| | value | index = nil |と同等です。これは| value、index |と一致しています。

ハッシュを扱うときは、値よりもキーに焦点を当てることが多く、通常はkey => valueまたはhash[key] = valueのいずれかの順序でキーと値を扱います。

ダックタイピングが必要な場合は、Brent Longboroughが示したように定義済みのメソッドを使用するか、maxhawkinsが示したような暗黙的なメソッドを使用してください。

Rubyは、プログラマが言語に合うように対応するのではなく、プログラマに合うように言語を対応させることがすべてです。これがたくさんの方法がある理由です。何かを考えるための方法はたくさんあります。 Rubyでは、最も近いコードを選択し、残りのコードは通常非常にきちんと簡潔に表示されます。

元の質問「Rubyで配列を反復処理するための「正しい」方法は何ですか?」については、コアの方法(強力な構文糖衣やオブジェクト指向の力がない場合)は、次のようにすることだと思います。

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

しかし、Rubyは強力な構文糖とオブジェクト指向の力に関するものですが、とにかくここではハッシュと同等のものであり、キーは順序付けされていてもいなくてもかまいません。

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

だから、私の答えは、「Rubyで配列を反復処理する「正しい」方法は、あなた(つまりプログラマまたはプログラミングチーム)とプロジェクトにかかっている」です。より良いRubyプログラマーはより良い選択をします(どの統語力および/またはどのオブジェクト指向アプローチの)。より良いRubyプログラマーは、もっと多くの方法を探し続けています。


さて、私は別の質問をしたいのですが、「RubyのRangeを逆方向に繰り返すための「正しい」方法は何ですか?」 (この質問は私がこのページに来た方法です。)

それは(転送のために)するのはいいことです:

(1..10).each{|i| puts "i=#{i}" }

しかし、私はしたくありません(後方へ)。

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

実際、それほどやりすぎても構いませんが、逆に教えるときは、生徒にナイス対称性を見せたい(たとえば、差を最小限にして、逆の追加やstep -1など、違いはありません)。他の何かを変更する。あなたは(対称のために)することができます:

(a=*1..10).each{|i| puts "i=#{i}" }

そして

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

私はあまり好きではないが、あなたはできない

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" }   # I don't want this though.  It's dangerous

あなたは最終的にすることができます

class Range

  def each_reverse(&block)
    self.to_a.reverse.each(&block)
  end

end

しかし、(まだ)オブジェクト指向のアプローチではなく、純粋なRubyを教えたいです。逆方向に繰り返したいと思います。

  • 配列を作成せずに(0..1000000000を考慮)
  • 任意の範囲で機能する(例:整数だけでなく文字列)
  • 余分なオブジェクト指向の力を使わずに(つまりクラスを変更しないで)

predメソッドを定義しなければこれは不可能であると私は信じています。あなたがこれをすることができるならば私に知らせてください、さもなければそれが残念であるけれども不可能の確認は評価されるでしょう。おそらくRuby 1.9がこれに対処しています。

(これを読んでくれてありがとう。)

56
Luis Esteban

両方が必要な場合はeach_with_indexを使用してください。

ary.each_with_index { |val, idx| # ...
17
J Cooper

他の答えは大丈夫ですが、私はもう一つの周辺的な事柄を指摘したかった:配列は順序付けられているがハッシュは1.8にはない。 (Ruby 1.9では、ハッシュはキーの挿入順で並んでいます。)1.9の前は、配列と同じ方法/順序でHashを反復するのは意味がありません。 PHP連想配列のデフォルトの順序が何であるかわかりません(明らかに、私のgoogle fuもそれを理解するのに十分なほど強力ではありません)。連想配列の順序は定義されていないように見えるので、このコンテキストではPHP arrayとPHP連想配列は「同じ」になります。

そういうものとして、Rubyのやり方は私にとってより明確で直感的に思えます。 :)

12
Pistos

同じことを配列とハッシュで一貫してやろうとすること おそらく ただコード臭いですが、一貫した振る舞いを探しているなら、私は同質の半猿パッチャーとしてブランドされる危険性があるので、これはトリックですか?

class Hash
    def each_pairwise
        self.each { | x, y |
            yield [x, y]
        }
    end
end

class Array
    def each_pairwise
        self.each_with_index { | x, y |
            yield [y, x]
        }
    end
end

["a","b","c"].each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
    puts "#{x} => #{y}"
}
7

私はハッシュを使ってメニューを構築しようとしていました( Camping Markaby )。

各項目には2つの要素があります: メニューラベル _ url _ なので、ハッシュは正しく見えましたが、 'Home'の '/' URLは最後に表示されます(メニュー項目は間違った順序で表示されます。

each_sliceで配列を使うことは仕事をします:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

各メニュー項目に追加の値を追加する(たとえば、CSS _ id _ nameのように)とは、スライス値を増やすことを意味します。それで、ハッシュと似ていますが、任意の数の項目からなるグループがあります。完璧です。

それで、これは誤って解決策をほのめかしてくれてありがとう!

明らかですが、述べる価値があります。配列の長さがスライスの値で割り切れるかどうかをチェックすることをお勧めします。

5
Dave Everitt

ここにあなたの質問にリストされている4つの選択肢があります。あなたはあなたが必要とするものによって異なるものを使いたくなるかもしれません。

  1. 単に値を調べます。

    array.each
    
  2. 単純にインデックスを調べます。

    array.each_index
    
  3. インデックス+インデックス変数をたどります。

    for i in array
    
  4. 制御ループ数+インデックス変数:

    array.length.times do | i |
    
5
Jake

enumerable mixin(Railsがそうであるように)を使えば、リストされているphpスニペットに似たことをすることができます。 each_sliceメソッドを使用してハッシュを平坦化するだけです。

require 'enumerator' 

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

より少ない猿パッチが必要です。

ただし、これは再帰配列または配列値を持つハッシュがある場合には問題になります。 Ruby 1.9では、この問題は、再帰の深さを指定するflattenメソッドのパラメータで解決されています。

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

これがコードの匂いかどうかという疑問に関しては、私にはわかりません。通常、私が後ろに曲がって何かを繰り返すために私が後退して、私が問題を間違って攻撃しているのに気づかなければならないとき。

3
maxhawkins

正しい方法は、あなたが最も快適に感じる方法であり、それはあなたがそれがやりたいことを行います。プログラミングでは、物事を行うための1つの「正しい」方法はめったにありません。より多くの場合、選択する方法が複数あります。

あなたが物事のやり方の特定の方法に慣れている場合は、それが機能しない場合を除き、ちょうどそれをやってください - それからそれはより良い方法を見つけるための時間です。

Ruby 2.1では、each_with_indexメソッドは削除されました。代わりに each_index を使用できます。

例:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

生成します:

0 -- 1 -- 2 --
2
Amjed Shareef