10
k = [1,2,3,4,5]
for n in k
  puts n
  if n == 2
    k.delete(n)
  end
end
puts k.join(",")

# Result:
# 1
# 2
# 4
# 5
# [1,3,4,5]

# Desired:
# 1
# 2
# 3
# 4
# 5
# [1,3,4,5]

これと同じ効果が、他の配列反復子 k.each でも発生します。

k = [1,2,3,4,5]
k.each do |n|
  puts n
  if n == 2
    k.delete(n)
  end
end
puts k.join(",")

同じ出力があります。

これが起こっている理由は非常に明確です...Rubyは配列に格納されたオブジェクトを実際に反復処理するのではなく、インデックス0から開始し、終了するまで毎回インデックスを増やして、きれいな配列インデックスイテレータに変換します。 . ただし、アイテムを削除してもインデックスはインクリメントされるため、同じインデックスを 2 回評価することはありません。

これは起こっていることではないかもしれませんが、私が考えることができる最高のものです.

これを行うためのきれいな方法はありますか?これを実行できる組み込みイテレータは既にありますか? それとも、アイテムが削除されたときにインクリメントせずに、それを汚して配列インデックス イテレータを実行する必要がありますか? (または、配列のクローンを反復処理し、元の配列から削除します)


明確化

単に配列から項目を削除したいだけではありません。それが明らかだったらごめんなさい。私がやりたいことは、各要素を繰り返し処理し、それを「処理」することです。このプロセスは時々それを削除するかもしれません。より正確に言うと:

class Living_Thing

  def initialize tracker,id
    @tracker = tracker
    @id = id

    @tracker << self
  end

  def process
    do_stuff
    puts @id
    if @id == 2
      die
    end
  end

  def die
    do_stuff_to_die
    @tracker.delete(self)
  end

  def inspect
    @id
  end
end

tracking_array = Array.new()

foo = Living_Thing.new(tracking_array,1)
bar = Living_Thing.new(tracking_array,2)
rab = Living_Thing.new(tracking_array,3)
oof = Living_Thing.new(tracking_array,4)

puts tracking_array.join(",")              # => [1, 2, 3, 4]

for n in tracking_array
  n.process
end

# result: only foo, bar, and oof are processed

理想的には、tracking_array 内のすべてのアイテムを処理したいと思います。

Living_Thing が tracking_array から削除されたら、Living_Thing#die を呼び出す必要があります。do_stuff_to_die は、クラン化する必要があるものをクリーンアップします。

4

3 に答える 3

15

ほとんどの言語では、反復中にコレクションを変更するのはエラーです。ほとんどの言語では、解決策はコピーを作成してそれを変更するか、インデックスのリストを作成し、反復が完了したときにそれらのインデックスに対して操作を実行することですが、Ruby の反復子はそれ以上のことを提供します。いくつかの解決策は明らかです。最も慣用的な IMO:

puts k
puts k.reject {|n| n == 2}.join(',')

あなたの例からより直接的に翻訳された:

k.delete_if do |n|
  puts n
  n == 2
end
puts k.join(',')

(delete_ifは基本的に の破壊的なバージョンでありreject、ブロックが true を返さなかったオブジェクトの配列を返します。)

于 2010-06-09T03:55:26.960 に答える
4

これは、処理により適している可能性があります (参照。更新された説明)

k = [1,2,3,4,5] 
k.dup.each do |n| 
  puts n 
  if n == 2
    k.delete(n) 
  end 
end 
puts k.join(",")

それはあなたが持っていた質問を脇に置きます(オブジェクトを介した反復とインデックスを介した反復について)

于 2010-06-09T04:31:33.747 に答える
3

さて、配列からすべての 2 を削除したいとしましょう:

arr = [1,2,3,4,5]
arr.delete(2)
puts arr.join(", ")
# => "1, 3, 4, 5"
arr = [1,2,3,2,4,2,5,2]
arr.delete(2)
puts arr.join(", ")
# => "1, 3, 4, 5"

しかし、反復したいと思うので、次のようにします。

arr = [1,2,3,4,5]
arr.each {|x| a[a.index(x)] = nil if x == 2}.compact!

もしかして汚すぎる?nil への代入は反復子の数を正しく保ち、事後に nil をcompact!一掃します。コースmapはそれを少し短く保ち、よりきれいにします:

arr.map {|x| x if x != 2}.compact!
于 2010-06-09T03:52:01.633 に答える