tables, table.unpack and nil values
|
unpack(t, i, j) returns t[i], t[i+1], ..., t[j]; if you do not specify i or j, they're defaulted to 1 and #t respectively -- so whether unpack will return values from an array with holes in it depends on the behavior of #t.
Quote:
On a tangential note, ipairs explicitly iterates over t[1], t[2], t[3], ... stopping at the first nil value rather than using the length operator, so it'll never make it past the first nil in the array. |
There are quirks in how Lua stores tables internally. Every table is created with 2 data arrays, one for sequential data and another for associative data. Sequential data is stored when you assign a non-nil value to the next index in a table, this index is equal to #t+1. If you assign data to a key that is greater than the next index or is a non-number, the data is stored in the associative array. Both the length operator and ipairs() operate exclusively off the sequential array.
The table constructor { } breaks this rule in its own way in which undefined keys are written directly into the sequential array and may include embedded nils. The table constructor also trims any nils from the right side of the list. There is another quirk in Lua that allows you to manually force nils to be embedded into the sequential array of a table. This is done by inserting two non-nil values then overwriting the first with nil. |
http://www.lua.org/source/5.2/ltable.c.html#luaH_getn
{x,y,z} seems to be an "array" notation, which makes sense to have (for example to store function arguments with holes in a table). When you construct table that way, sizearray variable is filled without checking about holes. Later, when you request table length, it just checks if last element according to sizearray is nil, and if it is, tries to find boundary(non-nil value followed by nil value) manually. Like in t2 example, it might not be the the boundary you want, so you better strip nils yourself in this case. Code:
local t3 = {nil, nil, 4, 7, nil, 4} Edit: i'm late |
Quote:
Lua Code:
Running the following code allows it to resize the table a few times to give it a chance to recalculate the sizearray value, but the embedded nils are still counting towards the size. Code:
local t=tAppend({},1,nil,nil,4,nil,6,7,8); To make the process better understood, this is the same as the following: Code:
local t={}; |
Code:
local t=tAppend({},1,nil,nil,4,nil,6,7,8); |
Quote:
All of my observations have been tested on the current implementation of Lua running on WoW. |
Well this answers a lot for me. Thank you Foxlit, SDPhantom and d87 for the, very clear explanation
Although I had some difficulty trying to understand about the length operator and the "embedded nils" in the table constructor, and the difference with ipairs But especially Lur's example was one of the weirdest things about Lua that prompted me to ask this question |
something on unpack that I didn't see above is for nils is in order for it to properly unpack the table all the values should be assigned at one time like the following
local temp = { nil, 1, nil, nil, nil, "blah", whatever } if they are assigned separately it would not unpack right but as a single declaration the following would work fine local var1, var2, var3, var4, var5, var6, var7 = unpack( temp ) |
Quote:
The second and third paragraphs of my post preceding that also describes exactly what you said and a workaround for it. Note calling tAppend(table,nil) will produce an undefined result, the list of values needs to end with a non-nil value. |
Quote:
|
Quote:
Quote:
|
try this to append items to your table so it will be "unpack"able
so put your table as 1st arg and values to add as 2nd, 3rd, ... Code:
tAppend = function( myTable, ... ) Code:
tSize = function( myTable ) Code:
tSize = function( myTable ) |
The original idea of the function was to preserve the existing contents of the table and add new values to the end, hence the name tAppend(). Though it would be easy to wrap the table constructor in a function, there is nothing to gain from doing so. Any other possible ways to achieve the original design would risk a stack overflow and/or cause huge spikes in memory usage. No other method I could think of would only work on modifying the original table as intended.
|
for everything I have read on unpack it appears the method of recreating the table as I did above ( with or without the function wrapper ) is the only easy way to guarantee a proper unpack when you have nils within your table.
http://www.wowwiki.com/API_unpack gives a few ideas but nothing that would really allow you to add on easily to the table on the fly |
Quote:
|
I believe the thing to do then would be the following to get all of the values from unpack
myTable = { {unpack( myTable )}, ... } and as for only changing the local pointer... I myself normally use one table with all my addon values within it so I can access it using something like: myTable["Key"] I pass whatever function needs it as the table and the key as a string that allows me to access the table in functions and not a local copy... you could also do close to the same if the table is in the global namespace: _G["MyVariable"] just pass the table as a string |
Quote:
Due to the design specs of the library my function was taken from, creating new tables is not allowed and completely contradicts the purpose of the library in the first place. The library I had this code in is a table recycler for use in code that calls frequently, perhaps multiple times each rendering pass. The idea behind it is to mimic the table constructor accurately on an existing table. Using an existing table means there's no new tables building up in memory waiting for a garbage collection pass. An ability of this library is that deallocated tables are stored as weak values, meaning when the garbage collector does run, they are released to it if they haven't been reallocated. Note mimicking the table constructor accurately means including this undocumented effect. |
Quote:
Code:
myTable = { "one", "two", "three" } And you add the values "four" and "five" on the end: Code:
tAppend(myTable, "four", "five") Code:
myTable = { { unpack( { "one", "two", "three" } ) }, "four", "five" } Code:
myTable = { { "one", "two", "three" }, "four", "five" } You've just created a new table that still only has 3 values, and another new table containing the original three values. Now imagine what happens after you run that a couple of times: Code:
myTable = { Plus, if you were to do anything like this, you would never be able to use local references to your table. Add together the ridiculously convoluted structure you see above, the unnecessary overhead of performing a global lookup every time you access your table, and the significant memory waste of creating two new tables and discarding one table every time you append anything, and this is just horribly inefficient and unusable code that you should never even consider using. |
Nah... that was a thought that didn't work out lol
only way I could see it would have a to unpack the table, have a loop create a string to recreate the table values, append the added values to the string, and then loadstring it... only problems I see is then you are throwing out functions rapidly instead of tables, and wasting more cpu cycles and memory the only real question is how huge are these tables that you have to worry about the garbage collection? or is there another way to write the code so that you don't need to use so many tables? Oh well... I guess lua doesn't enjoy the nils as much as blizzard enjoys supplying them as returns. (although the logical sweetness of nil == false and not( nil or false) == true almost gives a 3rd logic state ) |
All times are GMT -6. The time now is 11:49 PM. |
vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI