I'm new to ruby, and clearly see and find online that the following fails:
arr = [10, 20, 30, 40]
arr.each.with_index do |elmt, i|
print "#{elmt}, #{i}, "
arr.delete_at(i) if elmt == 20
puts arr.length
end
Clearly the delete_at is interacting with the iterator, but I cannot find a clear description of how the iterator and delete_at work such that this is so. (BTW, I understand solutions that work - I'm not looking for a correct way to do this, I'm trying to understand the semantics such that I know why this is not doing what's expected.) For completeness, here's the output
10, 0, 4
20, 1, 3
40, 2, 3
=> [10, 30, 40]
lightbulb!!
It seems clear the delete_at
immediately adjusts the underlying data structure, left shifting all elements to the right of the deleted item. The array seems more like a linked list than an array. So the "next" item (here "30") is now arr[1] (which the iterator has already processed), and when the iterator increments to arr[2], it sees "40". arr[3] returns nil and puts seems to do nothing with nil.
look at: https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/include/ruby/intern.h
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/enumerator.c
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c
each gives you an enumerator, and the with_index works with the enumerator.
when you reach element 20 you print it out, and after that you erase it, and at this point Ruby effectively shifts down all elements in the array. Now the enumerator which is backed by the array picks up the next element which is 40 because everything got shifted down (30 was copied over 20, 40 was copied over 30 and array was resized)
take a look at: https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c#L3023 It's where the magic of moving the elements via a memmove happens.
Let's step through it:
arr = [10, 20, 30, 40]
enum0 = arr.each
#=> #<Enumerator: [10, 20, 30, 40]:each>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [10, 20, 30, 40]:each>:with_index>
We can see the contents of enum1
by converting it to an array:
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
This tells us that Enumerator#each (which will invoke Array#each ) will pass the four elements of the enumerator enum1
into the block, assigning them in turn to the block variables. The first is:
elmt, i = enum1.next
#=> [10, 0]
puts elmt, i
# 10
# 0
elmt == 20
#=> false
so arr.delete_at(i)
is not executed.
Neither arr
nor enum1
have been altered:
arr
#=> [10, 20, 30, 40]
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
each
now passes the next element of enum1
into the block:
elmt, i = enum1.next
#=> [20, 1]
elmt == 20
#=> true
so we execute:
arr.delete_at(i)
#=> [10, 20, 30, 40].delete_at(1)
#=> 20
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
Ah! So the enumerator has been changed as well as arr
. That makes perfect sense, because when the enumerator is created a reference to the original receiver is established, together with rules for what is to be be done with it. Changes to the receiver will therefore affect the enumerator.
We can use Enumerator#peek to see what will be the next element of enum1
that each
will pass into the block:
enum1.peek
#=> [40, 2]
So you see that each
moves on to the next indexed position, oblivious to the fact that an earlier element has been removed, causing the later elements to each shift down by one position, causing each
to skip [30,1]
.
elmt, i = enum1.next
#=> [40, 2]
elmt == 20
#=> false
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
At this point each
reaches the end of the enumerator, so it's job is finished. It therefore returns the original receiver, arr
, but that has been modified, so we get:
[10, 30, 40]
A better example might be:
arr = [10, 20, 20, 40]
where:
[10, 20, 40]
would be returned.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.