1

I am seeing some unexpected behaviour when using a table as an array in pico8 lua when compared to regular PUC-Rio lua

If I run the following code using PUC-Rio lua5.4.4 (ubuntu)

local t={}
for i=1,10 do t[i] = i*10 end
t[2]=nil
t[4]=nil
t[6]=nil
t[8]=nil
print()
for i=1,#t do print(t[i]) end

I get the expected output

10
nil
30
nil
50
nil
70
nil
90
100

However if i run the same code with pico-8 I get:

10

This appears triggered only when I delete (ie set to nil) the t[8] element. if I comment out that line then I get the expected on pico8

10
nil
30
nil
50
nil
70
80
90
100

It appears, in pico8 lua, that the #t size of the array changes to 1 when the t[8] element is set to nil.

Oliver Schönrock
  • 1,038
  • 6
  • 11

2 Answers2

3

Both are expected results, the length operator # in lua returns a number n where t[n] ~= nil and t[n+1] == nil, if there are holes (nil value) inside, the result is undefined.

To find the maximum numeric index, in lua 5.1 you can use table.maxn, in other versions you have to write one.

table.maxn = function(t)
    local n = 0
    for k, v in pairs(t) do
        if type(k) == 'number' and k > n then
            n = k
        end
    end
    return n
end
shingo
  • 18,436
  • 5
  • 23
  • 42
  • yes, good answer. Accepted. And table.getn is deprecated as well. I am an experienced programmer in statically typed compiled languages like C and C++ as well as scripting languages like python and php. This is quite a trap for people new to the language. "To delete an element set it to nil", but we fail to mention clearly that the array then forgets its #size, but still gives you "a guess" as an answer.. ?? table.maxn is linear complexity as well.. :-( – Oliver Schönrock Feb 02 '23 at 08:21
  • 2
    IMO people new to a language should read its manual first (especially for such odd symbol), the behaviour is mentioned (more and more detailedly) in the manual. The trap is because of inertial thinking. When you want to use a table as a bag in lua, this operator is quite efficient to find an empty slot. – shingo Feb 02 '23 at 09:07
  • No need to be defensive. I fully appreciated, before posting, that `#` behaves differently to `length()` in other languages. What really surprised me is that its behaviour not consistent, either between implementations or indeed within one implementation, After posting I found that the PUC-RIO implementation also sometimes displays just the first element, if you print them BEFORE setting elements to nil. In "foot gun" languages like C and C++ this is classic "undefined behaviour". Seemingly unrelated actions (like printing) appear to change the behaviour of basic operations. – Oliver Schönrock Feb 02 '23 at 09:43
0

It seem that the size operator #t is just not well defined in lua in the presence of nil values.

https://www.lua.org/pil/19.1.html

"undefined behaviour" in a scripting language.. Nice.

Oliver Schönrock
  • 1,038
  • 6
  • 11
  • 2
    Lua arrays do not have "border", they are infinitely long: all indices are filled with `nil` by default. For example, arrays `{1,nil,2}` and `{1,nil,2,nil,nil}` are the same. So, what is the length of this array: 3 or 5? :-) The question about array length does not have a correct answer. So, `#t` operator just gives you a guess. – ESkri Feb 02 '23 at 08:03
  • @ESkri yes, and table.getn is deprecated as well. I am an experienced programmer in statically typed compiled languages like C and C++ as well as scripting languages like python and php. This is quite a trap for people new to the language. "To delete an element set it to nil", but we fail to mention clearly that the array then forgets its `#size`, but still gives you "a guess" as an answer.. ?? – Oliver Schönrock Feb 02 '23 at 08:18
  • Actually, Lua does not have arrays. :-) They are just dictionaries with integer-only sequential keys. But after inserting `nil` in the middle it is just a dictionary. Why do you want to apply array operator on a dictionary? – ESkri Feb 02 '23 at 13:39
  • `table.getn` is deprecated because it is 100% equivalent to `#` operator (which is not deprecated). Just compressing the basic library. – ESkri Feb 02 '23 at 13:41
  • 1
    `This is quite a trap for people new to the language.` - Yes, Lua has as many pitfalls as C or other languages. "Lua is simple to learn" is just a marketing trick :-) Actually, Lua attracts people by looking deceptively simple at first sight. – ESkri Feb 02 '23 at 13:45
  • @ESkri Why? This was all about efficient removal while iterating over an "array" of objects in a game. This function is very good. https://stackoverflow.com/a/53038524/1087626 It efficently removes and also reuses the indeces such that they constitute a "valid array" in lua terms, which means the `#` operator still works. Internally it works similarly to c++ std::remove_if. I have tested that performance and it is very good, and extremely good if you use LuaJit (c++ is only ~2x faster than LuaJit). – Oliver Schönrock Feb 02 '23 at 14:33
  • @ESkri re `getn`: I thought `getn` and `setn` were attempts to store the length of the "array" in the `table`, using different, redundant techniques, like an extra `n` element in the `table`. Looks like this was abandoned. Also table.maxn was abandoned. And table.remove also falls into a category of trying to make it simpler. Lots of attempts to give a more "easy" interface for reliably getting a size of an "array" and/or keeping the indeces in odrer such that lua naturally does the right thing. In the end, just have to understand the quirks, and that's fine. – Oliver Schönrock Feb 02 '23 at 14:37
  • `an extra n element in the table. Looks like this was abandoned.` - This was not abandoned. It's up to you to decide will you use `n` for storing "border" of the array or the default concept of infinitely long arrays is ok for your task. – ESkri Feb 03 '23 at 10:13