Search code examples
lua

Safely remove items from an array table while iterating


This question is similar to How can I safely iterate a lua table while keys are being removed but distinctly different.

Summary

Given a Lua array (table with keys that are sequential integers starting at 1), what's the best way to iterate through this array and delete some of the entries as they are seen?

Real World Example

I have an array of timestamped entries in a Lua array table. Entries are always added to the end of the array (using table.insert).

local timestampedEvents = {}
function addEvent( data )
  table.insert( timestampedEvents, {getCurrentTime(),data} )
end

I need to occasionally run through this table (in order) and process-and-remove certain entries:

function processEventsBefore( timestamp )
  for i,stamp in ipairs( timestampedEvents ) do
    if stamp[1] <= timestamp then
      processEventData( stamp[2] )
      table.remove( timestampedEvents, i )
    end
  end
end

Unfortunately, the code above approach breaks iteration, skipping over some entries. Is there any better (less typing, but still safe) way to do this than manually walking the indices:

function processEventsBefore( timestamp )
  local i = 1
  while i <= #timestampedEvents do -- warning: do not cache the table length
    local stamp = timestampedEvents[i]
    if stamp[1] <= timestamp then
      processEventData( stamp[2] )
      table.remove( timestampedEvents, i )
    else
      i = i + 1
    end
  end
end

Solution

  • I'd avoid table.remove and traverse the array once setting the unwanted entries to nil then traverse the array again compacting it if necessary.

    Here's the code I have in mind, using the example from Mud's answer:

    local input = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p' }
    local remove = { f=true, g=true, j=true, n=true, o=true, p=true }
    
    local n=#input
    
    for i=1,n do
            if remove[input[i]] then
                    input[i]=nil
            end
    end
    
    local j=0
    for i=1,n do
            if input[i]~=nil then
                    j=j+1
                    input[j]=input[i]
            end
    end
    for i=j+1,n do
            input[i]=nil
    end