lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



On Sat, Nov 11, 2023 at 11:16 PM Родион Горковенко <rodiongork@gmail.com> wrote:
Friends, Ali Rezvani and Hugo, thanks for your suggestions - they
forced me to experiment further!

Difficulty was that unlike with example of logs, I need not just print
nils, but need to mimic the behavior of io.read which returns them to
caller.

I found solution, though it has some curious hack inside - it seems ok
to insert nils into table by index for example, but the table size
should be preallocated
to work properly.

You're relying on undocumented behavior. It also does not work if the last value in the table is 'nil'. There's really no need to do all of this. Two features you seem to have missed:

- table.pack puts the number of arguments it receives in field 'n' in the table, and that includes nil values.
- table.unpack has optional arguments to specify its size, if you use them it'll happily unpack nils for you.

If you want to iterate over a table range that may have nils in it do not use 'ipairs'.
Here's a function that takes its arguments and replaces the non-numeric ones with nil, and returns them.

local function numbers_only(...)
    local args = table.pack(...)
    local res = {}
    for i = 1, args.n do
        if type(args[i]) == "number" then
            res[i] = args[i]
        end
    end
    return table.unpack(res, 1, args.n)
end

print(numbers_only(1, 2, "three", 4, "five"))

Output: 1 2 nil 4 nil

This function does not rely on preallocating the sequence, and it does not matter in which order you build 'res'. #res is definitely not 5 in this example.

Table.pack and table.unpack appeared in Lua 5.2.
 
And now for some more advanced trickery. table.unpack uses the '#' operator to figure out the size of the table. I'm overriding it here:

local mt = {
    __len = function(t)
        return t.n or rawlen(t)
    end,
}

local function numbers_only(...)
    local args = setmetatable(table.pack(...), mt)
    local res = setmetatable({n = #args}, mt)
    for i = 1, #args do
        if type(args[i]) == "number" then
            res[i] = args[i]
        end
    end
    return table.unpack(res)
end

print(numbers_only(1, 2, "three", 4, nil, nil))


Hope this helps.