解决这个 ruby 迭代器棘手问题的干净解决方案吗?
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
理想情况下,我希望处理 track_array 中的所有项目。
当Living_Thing从tracking_array中删除时,必须调用Living_Thing#die; do_stuff_to_die 清理必须清理的东西。
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]
This same effect happens with the other array iterator, 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(",")
has the same output.
The reason this is happening is pretty clear...Ruby doesn't actually iterate through the objects stored in the array, but rather just turns it into a pretty array index iterator, starting at index 0 and each time increasing the index until it's over. But when you delete an item, it still increments the index, so it doesn't evaluate the same index twice, which I want it to.
This might not be what's happening, but it's the best I can think of.
Is there a clean way to do this? Is there already a built-in iterator that can do this? Or will I have to dirty it up and do an array index iterator, and not increment when the item is deleted? (or iterate through a clone of the array, and delete from the original array)
Clarification
I don't simply want to delete items from an array; sorry if that was clear. What I'd want to do is iterate through each element, and "process" it; this process might sometimes delete it. To be more accurate:
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
Ideally, I'd want all items in tracking_array to be processed.
When Living_Thing is removed from tracking_array, Living_Thing#die must be called; do_stuff_to_die cleans up things that have to be claned up.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在大多数语言中,在迭代集合时改变集合是错误的。在大多数语言中,解决方案是创建一个副本并对其进行变异,或者构建一个索引列表,并在完成迭代后对这些索引执行操作,但 Ruby 的迭代器为您提供的功能远不止这些。有几个解决方案是显而易见的。最惯用的IMO:
更直接地从您的示例翻译:(
delete_if
基本上是reject
的破坏性版本,它返回块未返回true的对象的数组为了。)It's an error in most languages to mutate a collection while you're iterating it. In most languages the solution is to create a copy and mutate that or build up a list of indexes and perform the operations on those indexes when you're done iterating, but Ruby's iterators give you a bit more than that. A couple of solutions are obvious. The most idiomatic IMO:
More directly translated from your example:
(
delete_if
is basically the destructive version ofreject
, which returns an array of the objects that the block did not return true for.)这可能更适合处理(参考更新的澄清)
它回避了您所遇到的问题(关于通过对象的迭代与通过索引的迭代)
this might be more suited for the processing (Ref. updated clarification)
it side steps the question you had though (about iteration through objects vs iteration through indices)
好吧,假设您想从数组中消除所有 2:
但我怀疑您想迭代,所以我会:
也许这太脏了?对 nil 的赋值使迭代器计数保持正确,并且
compact!
会在事后清除 nils。 Coursemap
使其变得更短并且更清晰:Okay, so let's say you want to eliminate all 2's from your array:
But I suspect you want to iterate, so I would:
Maybe that's too dirty? The assignment to nil keeps the iterator count right, and the
compact!
wipes out the nils after the fact. Coursemap
keeps it a little shorter and is cleaner: