views:

190

answers:

3
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.

+3  A: 

Okay, so let's say you want to eliminate all 2's from your array:

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"

But I suspect you want to iterate, so I would:

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

Maybe that's too dirty? The assignment to nil keeps the iterator count right, and the compact! wipes out the nils after the fact. Course map keeps it a little shorter and is cleaner:

arr.map {|x| x if x != 2}.compact!
todb
+13  A: 

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:

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

More directly translated from your example:

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

(delete_if is basically the destructive version of reject, which returns an array of the objects that the block did not return true for.)

Chuck
nice, I like delete_if very clean solution
Sam Saffron
Using delete_if to run a full code block for every item is pretty elegant; I like it a lot =) I'll be sure to keep it in my arsenal; however, for my current problem, it doesn't quite work =/
Justin L.
Or use `reject!` which is equivalent to `delete_if`.
Anurag
+3  A: 

this might be more suited for the processing (Ref. updated clarification)

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

it side steps the question you had though (about iteration through objects vs iteration through indices)

potatopeelings
thanks; i hadn't thought of using k.dup with .each in one line, instead of using a temp variable; this is the solution i'll be using for now...although, yeah, it does side-step the actual iteration deletion question.
Justin L.