2016-02-19 19 views
5

を反復しながら、私は2つのeachブロックとネストされた配列を反復し、内部反復の内側同じ配列から要素を削除しています:削除要素

arr = [1,2,3] 
arr.each do |x| 
    arr.each do |y| 
    puts "#{arr.delete(y)}" 
    end 
end 

これは結果13を生成します。配列は[2]になります。

2が最初または2番目のループに渡されないのはなぜですか?これはネストされた反復の副作用のようなものですか?

+0

適切な回答を承認してください – bronislav

+0

@bronislav、それに時間をください。 1時間しかありません。急いではありません。 –

+0

@CarySwoveland、ごめんなさい – bronislav

答えて

7

削除された要素のインデックスが原因です。私はあなたを表示するために、いくつかの出力を追加しました:

arr = [1,2,3] 
arr.each do |x| 
    puts "1: #{arr.inspect}, x: #{x}" 
    arr.each do |y| 
    puts "2: #{arr.inspect}, y: #{y}" 
    puts "#{arr.delete(y)}" 
    end 
end 

結果:

1: [1, 2, 3], x: 1 
2: [1, 2, 3], y: 1 
1 
2: [2, 3], y: 3 
3 
=> [2] 

最初に削除要素は、それぞれ内側のブロックで(インデックスが0である)1です。削除2がインデックス0を持つと、今度は各反復がインデックス1になり、これは要素3になります.3が削除され、それが反復の終了です。だからあなたは[2]になる。

同じことが各入れ子にすることなく、起こる:

arr = [1,2,3] 
arr.each do |x| 
    puts "1: #{arr.inspect}, x: #{x}" 
    puts "#{arr.delete(x)}" 
end 

結果:

1: [1, 2, 3], x: 1 
1 
1: [2, 3], x: 3 
3 
=> [2] 

私はこの動作を回避するために、このような操作のためのreverse_eachを使用することをお勧め:

arr = [1,2,3] 
arr.reverse_each do |x| 
    puts "1: #{arr.inspect}, x: #{x}" 
    puts "#{arr.delete(x)}" 
end 

結果:

1: [1, 2, 3], x: 3 
3 
1: [1, 2], x: 2 
2 
1: [1], x: 1 
1 
=> [] 
+1

+1。あるいは、これらの状況で 'select'または' reject'を使うことだけでも構いません – cozyconemotel

1

この場合を理解するには、単純な配列をインデックスで横断する場合を考えてみましょう。

[1,2,3]の配列があります。

0で反復を開始すると、現在の要素は1になります。次に、インデックス0の要素1を削除すると、配列は[2,3]になります。

次の反復では、インデックスは1になり、それは3を指します.3が削除されます。あなたの配列は[2]になります。

インデックスは2、配列の長さは1です。したがって、何も起こりません。 この内部ループが完了すると、外部ループは更新されたインデックス1で再開し、2に戻ります。また、配列の長さが1であるため、それらは実行されません。

だからこそ、これは、インデックスを反復として使用しているようです。

私の知る限り、C++のような未定義の動作があるはずです(このようなコードはお勧めできません)。現在の要素を削除すると反復する間に、ポインタ値(現在はeachに渡された関数ブロックのパラメータに保持されている)が破損するためです。

+0

あなたの最初の段落はあなたの意見/機能要求です。残りの部分とは別に保管してください(恐らくパラネシス内の最後に置いてください)。私はそれが簡潔な答えにもかかわらずあなたがupvotedになっていない理由だと思う。あなたはまた外側のループに言及していません(少なくともあなたはそれがどのように効果がないか説明する必要があります)。建設的な提案の – sawa

2

ネスティングとは関係ありません。実際には、内側のループだけで同じ結果が得られます。

arr = [1,2,3] 
arr.each do |y| 
    puts "#{arr.delete(y)}" 
end 
# => outputs 1, 3 
a # => [2] 

コンパイルは繰り返し中に配列を変更するためです。


理由は、Array#eachがインデックスに基づいているためです。まず、x1になります(これは結果とはまったく関係ありません)。内側のループ内では、まず、あなたが持っている:

  • a[1, 2, 3]index0y1
indexは、内部反復のベースとなるインデックスがあり

、そしてあなたがyを削除し、あなたが得る:

  • a[2, 3]
  • 次の内部反復で

、あなたが持っている:

  • a[2, 3]index1y:反復がインデックスに基づいているため2がスキップされ3

に留意されたいです。( 1)。

  • a[2]を次に、3を与える、削除されます。

外側のループインデックス1で次の反復を試みる、aに残さ十分な要素が存在しないので、それが終了します。 eachがインデックスを反復し、内側の要素を削除しているため、loopの次の繰り返しの要素が削除されているため、

0

があります。要素の数を増やし、ループ内の反復の現在のインデックスを含めれば、より大きな画像を見ることができます。

arr = [1,2,3,4,5,6,7,8,9] 
arr.each_with_index do |x,ix| 
    puts "loop1: #{arr.inspect}, x: #{x}, ix: #{ix}" 
    arr.each_with_index do |y, iy| 
    puts "loop2: #{arr.inspect}, y: #{y}, iy: #{iy}" 
    puts "#{arr.delete(y)}" 
    end 
end 

結果

loop1: [1, 2, 3, 4, 5, 6, 7, 8, 9], x: 1, ix: 0 
loop2: [1, 2, 3, 4, 5, 6, 7, 8, 9], y: 1, iy: 0 
1 
loop2: [2, 3, 4, 5, 6, 7, 8, 9], y: 3, iy: 1 
3 
loop2: [2, 4, 5, 6, 7, 8, 9], y: 5, iy: 2 
5 
loop2: [2, 4, 6, 7, 8, 9], y: 7, iy: 3 
7 
loop2: [2, 4, 6, 8, 9], y: 9, iy: 4 
9 
loop1: [2, 4, 6, 8], x: 4, ix: 1 
loop2: [2, 4, 6, 8], y: 2, iy: 0 
2 
loop2: [4, 6, 8], y: 6, iy: 1 
6 
=> [4, 8] 

もしループ中インデックス各反復がインクリメントされるが、配列は一つの要素短い後削除されるので、それは次の(およびすべての)マッチングを削除するため要素が利用可能であり、最後にループが比較してループを停止するときindex >= length