Changing a collection while walking it
A loop and the collection it walks share the collection's storage. Changing the collection's shape, adding or removing elements, while the loop runs breaks that sharing in ways that differ from one collection to the next.
#import "std/containers"
'' An array's bounds are read once, before the first pass.
dim queue( any ) as int32
redim queue( 0 to 2 )
queue( 0 ) = 1 : queue( 1 ) = 2 : queue( 2 ) = 3
for each q in queue
print q;
if q = 2 then
redim preserve queue( 0 to 3 ) '' a fourth element...
queue( 3 ) = 4
end if
next
print " -"; ubound( queue ) + 1; " elements, the last never visited"
'' To change a collection's shape, collect the changes during the
'' walk and apply them after it.
dim stock as containers.OrderedMap( of ustring, int32 )
stock.Put( "apples", 3 )
stock.Put( "pears", 0 )
stock.Put( "plums", 0 )
stock.Put( "figs", 7 )
dim soldOut as containers.Array( of ustring )
for each item in stock
if item.value = 0 then soldOut.Push( item.key )
next
for each gone in soldOut
stock.Remove( gone )
next
for each item in stock
print item.key; ":"; item.value; " ";
next
print
Output
1 2 3 - 4 elements, the last never visited
apples: 3 figs: 7
Over an array, for each reads the bounds once, before the first pass,
like the counted loop it compiles to. Growing the array inside the loop does
not extend the walk: the fourth element exists afterwards and is never visited.
Shrinking it inside the loop does not shorten the walk either: the loop goes
on to the bound it read, past the end of what is left, and -exx does not
catch it.
A library container's iterator holds a position in the container's storage,
and adding or removing elements can move that storage. Do not change a
container's shape while for each walks it. Changing an element's value
through a byref loop variable is fine; that is what byref is for.
The second half shows the safe pattern: the loop over stock only collects
the keys to remove in soldOut, and a second loop removes them once the first
has finished.
A byref loop variable bound to an array element points into the array's
storage. redim preserve can move that storage, and after it the variable
refers to where the element used to be.
Activities
- Use the same pattern to double the stock of every item with fewer than 5,
adding a new
"<item> (reorder)"entry for each. - Rewrite the first loop so that it does visit the fourth element: a
doloop that testsubound( queue )on every pass.