How to remove elements inside a table based on a value of those elements?

41 Views Asked by At

Iam trying to remove elements in a table which have a certain value

test = {
    {
        id = 473385593,
        deleted = true,
    },
    {
        id = 473385619,
        deleted = true,
    },
    {
        id = 473432275,
        deleted = false,
    },
    {
        id = 473786710,
        deleted = false,
    },
}

for index, value in ipairs(test) do
    if value.deleted then
        table.remove(test, index)
    end
end

for _, value in pairs(test) do
    print(value.id)
    print(value.deleted)
end

I would like that i the output is

473432275
false
473786710
false

But

{
        id = 473385619,
        deleted = true,
    },

is not deleted

So i guess as im iterating over the table to access the value and then do something when deleted == true i remove that element but also reorder the table and so i skip the next element after i call table.remove. At least thats what i figured whats happening.

So how do i remove in lua a tables elements based on their value?

1

There are 1 best solutions below

0
Luatic On BEST ANSWER

As you figured out already, this snippet

for index, value in ipairs(test) do
    if value.deleted then
        table.remove(test, index)
    end
end

is doing the grave mistake of removing table elements - shifting subsequent elements down - while iterating a table. for index, value in ipairs(test) do <body> end is roughly equivalent to

do
    local index = 0
    while true do
        index = index + 1
        local value = test[index]
        if value == nil then
            break
        end
        <body>
    end
end

So of course if <body> does a table.remove(test, index), then in the next iteration test[index] will be the previous test[index+1] and you will skip an element.

I would suggest "filtering" test, and building a new table containing only the elements you want to keep. This would look as follows:

local test_filtered = {}
for index, value in ipairs(test) do
    if not value.deleted then
        table.insert(test_filtered, value)
    end
end
for _, value in ipairs(test_filtered) do
    print(value.id)
    print(value.deleted)
end

This is asymptotically optimal runtime-wise (linear time). Very often it's cleaner to not mutate the original table; that can lead to surprises.

In case you really want to modify test, you could iterate in reverse order, e.g. using for i = #test, 1, -1 do. Then table.remove will work fine. But be warned that this has a time complexity bound of O(n²).

By keeping a second index you can also do in-place bulk removal of elements in O(n).

Also: Why do you use a "list" here at all? Assuming that you don't care about order, a "hash table" (which lets you remove elements while iterating) would make more sense, given that you have IDs. Consider

test = {
    [473385593] = {deleted = true},
    [473385619] = {deleted = true},
    [473432275] = {deleted = false},
    [473786710] = {deleted = false},
}

then you could just do

for id, value in pairs(test) do
    if value.deleted then
        test[id] = nil
    end
end