manabreak
manabreak

Reputation: 5597

How to remove all 'nil' values from an array?

I have an array of objects (or just numbers), and I have another array which contains all the objects that should not be removed from the first array in any circumstances. It looks something like this:

-- Array of objects (just numbers for now)
Objects = {}

-- Array of objects that should always stay in the 'Objects' array
DontDestroyThese = {}

-- Populate the arrays
Objects[#Objects+1] = 1
Objects[#Objects+1] = 2
Objects[#Objects+1] = 3
Objects[#Objects+1] = 4
Objects[#Objects+1] = 5

DontDestroyThese[#DontDestroyThese+1] = 2
DontDestroyThese[#DontDestroyThese+1] = 5

Now, I have a method called destroy() that should remove all objects from the Objects array except those included in the DontDestroyThese array. The method looks something like this:

function destroy()
    for I = 1, #Objects do
        if(DontDestroyThese[Objects[I]] ~= nil) then
            print("Skipping " .. Objects[I])
        else
            Objects[I] = nil
        end
    end
end

However, as the result, the Objects array now contains nil values here and there. I'd like to remove these nils so that the Objects array would consist only of the numbers that were left there after calling destroy(). How do I do that?

Upvotes: 3

Views: 6240

Answers (3)

tonypdmtr
tonypdmtr

Reputation: 3225

I think the solution is much simpler. To remove any nils ('holes' in your array), all you need to do is iterate your table using pairs(). This will skip over any nils returning only the non-nil values that you add to a new local table that is returned in the end of the 'cleanup' function. Arrays (tables with indices from 1..n) will remain with the same order. For example:

function CleanNils(t)
  local ans = {}
  for _,v in pairs(t) do
    ans[ #ans+1 ] = v
  end
  return ans
end

Then you simply need to do this:

Objects = CleanNils(Objects)

To test it:

function show(t)
  for _,v in ipairs(t) do
    print(v)
  end
  print(('='):rep(20))
end

t = {'a','b','c','d','e','f'}
t[4] = nil          --create a 'hole' at 'd'
show(t)             --> a b c
t = CleanNils(t)    --remove the 'hole'
show(t)             --> a b c e f

Upvotes: 6

moteus
moteus

Reputation: 2235

local function remove(t, pred)
  for i = #t, 1, -1 do
    if pred(t[i], i) then
      table.remove(t, i)
    end
  end
  return t
end

local function even(v)
  return math.mod(v, 2) == 0
end

-- remove only even numbers
local t = remove({1, 2, 3, 4}, even)

-- remove values you want
local function keep(t)
  return function(v)
    return not t[v]
  end
end

remove(Objects, keep(DontDestroyThese))

Upvotes: 0

Alan
Alan

Reputation: 3002

The most efficient way is probably to create a new table to hold the result. Trying to move values around in the array is likely to have a higher overhead than simply appending to a new table:

function destroy()
    local tbl = {}
    for I = 1, #Objects do
        if(DontDestroyThese[Objects[I]] ~= nil) then
            table.insert(tbl, Objects[I])
        end
    end
    Objects = tbl
end

This method also means you don't have to deal with altering the contents of the table/array you're iterating over.

Upvotes: 5

Related Questions