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开始,每次增加索引直到结束. 但是当你删除一个项目时,它仍然会增加索引,所以它不会两次评估同一个索引,这是我想要的。

可能不是正在发生的事情,但这是我能想到的最好的。

有没有一种干净的方法可以做到这一点?是否已经有一个内置的迭代器可以做到这一点?还是我必须把它弄脏并做一个数组索引迭代器,而不是在删除项目时增加?(或遍历数组的克隆,并从原始数组中删除)


澄清

我不只是想从数组中删除项目;抱歉,如果这很清楚。我想要做的是遍历每个元素,并“处理”它;这个过程有时可能会删除它。更准确地说:

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 的赋值使迭代器计数保持正确,并且在compact!事后清除 nil。课程map让它更短,更干净:

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