Thread Tools Display Modes
09-18-12, 04:40 PM   #1
Kcir
A Defias Bandit
 
Kcir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2011
Posts: 3
Question Nested tables vs multiple tables

I haven't dealt with nested tables any since I started programming in lua but I have a suspicion that it would be more efficient than what I'm doing now, which is using mulitple tables (most are global).

I have ran into the more than 60 upvalues error on more than one occasion and that is a red flag to me that I'm doing something wrong... In visual basic and pascal I never remember having issues similar to this so I'm trying to rewrite a lot of the main code to accomodate.

The main question I have is which is better nested tables or multiple tables?
If it is nested how do I impliment them properly? Also, what is the best way to release data from a table after using it? I have been using wipe() but I don't know if that is the only option I have or if it is the best way...

Most of the info I have found is still a bit confusing (mabey cause I'm still new to the process)

Any help is greatly appreciated.

Kcir
__________________
If practice makes perfect and nobodies perfect, why practice?
Because practice makes better.
  Reply With Quote
09-18-12, 05:12 PM   #2
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,326
Originally Posted by Kcir View Post
I have ran into the more than 60 upvalues error on more than one occasion and that is a red flag to me that I'm doing something wrong... In visual basic and pascal I never remember having issues similar to this so I'm trying to rewrite a lot of the main code to accomodate.
Generally, the more tables total you create, the more memory you end up using. Also remember table lookups take more time than accessing locals/upvalues. Accessing the global environment is always a table lookup. If you're hitting the upper limit for locals (I never did honestly), you really should take a look at your code and see where you can move locals into the correct scope.

I don't know about Pascal, but VB doesn't technicly have a lexical scope. Variables are either public to the entire project, private to a specific file, or defined as a static in a sub/function. As for Lua, there's the global environment, which can be seen as the same as a VB public variable. Locals and upvalues are available to whatever scope they're defined in.

Scopes are practicly blocks of code inside a block enclosure. These include functions, if...then...end, for...end, while...end, repeat...until, and specificly to create a scope without a condition or loop mechanic, do...end. Scopes can be nested, in which locals defined in a higher scope are called upvalues.

Originally Posted by Kcir View Post
Also, what is the best way to release data from a table after using it? I have been using wipe() but I don't know if that is the only option I have or if it is the best way...
This depends on how often you're going to be using the table or try to recreate it. There are limits on what you can do to an existing table, such as you cannot use the table constructor to provide initial values anymore. This will always create a new table. If you're going to use the table frequently such as in an OnUpdate script or in an event that fires a lot, then using table.wipe() (same as wipe()) and reassigning data to it would be the thing to do. Otherwise, you can assign nil to the variable containing the pointer to release it for the GarbageCollector to grab later. Note if the pointer is stored in a local in which execution has exited the scope of, the table will be released to the GarbageCollector automaticly.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 09-18-12 at 05:28 PM.
  Reply With Quote
09-19-12, 09:10 AM   #3
myrroddin
A Pyroguard Emberseer
 
myrroddin's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2008
Posts: 1,240
Sorry for jumping in, but the question about table.wipe/wipe made me think to ask if that is the most efficient method to clear a table. I'm assuming the larger the table, the better the example.
Lua Code:
  1. local someTable = {} -- can be any size up to Lua's maximum, but assume there is a LOT of data
  2. wipe(someTable)
  3.  
  4. -- or this method
  5. local someTable = {} -- same parameters
  6. for i = 1, #someTable do
  7.     someTable[i] = nil
  8. end
I have heard the second method is faster and is better from a CPU side.
  Reply With Quote
09-19-12, 11:36 AM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,326
From a coding standpoint, I'm not entirely sure if the code as written in the second method would work as intended as the length would change to 0 when you wipe the first element. It is undocumented on whether the length would be resolved on first encounter or on every loop check. What I would do iterate from initial length to 1 with a step of -1.
Code:
for i=#someTable,1,-1 do
Judging the ideas of both methods, without performance testing, I'd say using wipe() would be faster since it passes it on to C side instead of looping in Lua. Note when you have the same exact code written in C and in Lua, C will always be exponentially faster than Lua.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 09-19-12 at 11:43 AM.
  Reply With Quote
09-19-12, 03:54 PM   #5
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
For emptying a table with a loop, you would want to use pairs, not ipairs or a loop based on #myTable. The latter methods will only get sequential indexed values; the former doesn't care what kind of keys you're using, if there are holes in your key sequence, or if you remove things in the middle of it.

Also, pairs works on indexed tables too, it just doesn't return the values in order by their indices.

Code:
local t = { "apples", "bananas", "coconuts" }

for k, v in pairs(t) do
	t[k] = nil
end
However, without doing any actual tests, I'd guess that wipe is faster than a loop using pairs or ipairs -- because both methods require 1 function call, but a (i)pairs loop also does other stuff -- but probably slower than a for i = 1, #t loop for small tables because no function call is involved (and function calls are basically the slowest thing you can do). At some point a function-less loop probably becomes slower, but I'm not sure where that point is. Unless you're emptying tables in an OnUpdate or something, though, I'd just use wipe because it's more concise.

On a related note, that I think is relevant because it illustrates the table lookup vs function call speed difference, I see a lot of addons using unpack to get color values from their settings tables:
Code:
local color = { 1, 0, 0 }
local r, g, b = unpack(color)
... but it's actually faster to do three table lookups instead of one function call:
Code:
local color = { 1, 0, 0 }
local r, g, b = color[1], color[2], color[3]
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
09-19-12, 04:51 PM   #6
Kcir
A Defias Bandit
 
Kcir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2011
Posts: 3
On that note...

Alright I think you guys have explained what is most efficient way of emptying a table, but I'm still wondering... I am cataloging relevant stats on items I'm currently wearing and this is how I'm doing it right now.

Lua Code:
  1. local myAtkPwr = {};
  2. local myArmor = {};
  3. local myStamina = {};
  4. local myStrength = {};
  5. local myDPS = {};

Is this the same as this?
Lua Code:
  1. local myItem = {};
  2. myItem[0].myArmor = 56;
  3. myItem[0].myStamina = 10;
  4. myItem[0].myStrength = 21;

I'm populating the tables via function calls and for every index (like 0) I have all the stats for 1 item (like chest piece).
Lua Code:
  1. function populateMyStats(myItemLnk, mySlotNumber)
  2.  
  3.   local stats = GetItemStats(myItemLnk)
  4.  
  5.   for stat, value in pairs(stats) do
  6.     if _G[stat] == ITEM_MOD_STRENGTH_SHORT then
  7.         myStrength[mySlotNumber] = value;
  8.     elseif _G[stat] == ITEM_MOD_STAMINA_SHORT then
  9.         myStamina[mySlotNumber] = value;
  10.     elseif _G[stat] == ARMOR then
  11.         myArmor[mySlotNumber] = value;
  12.     else
  13.      
  14.     end --if
  15.    
  16.   end --for
  17. end

Every index number is tied directly to the slot number that correspondes with the item.

Which is the better way of accomplishing this?
__________________
If practice makes perfect and nobodies perfect, why practice?
Because practice makes better.
  Reply With Quote
09-19-12, 07:46 PM   #7
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,326
Originally Posted by Kcir View Post
Alright I think you guys have explained what is most efficient way of emptying a table, but I'm still wondering... I am cataloging relevant stats on items I'm currently wearing and this is how I'm doing it right now.

Lua Code:
  1. local myAtkPwr = {};
  2. local myArmor = {};
  3. local myStamina = {};
  4. local myStrength = {};
  5. local myDPS = {};

Is this the same as this?
Lua Code:
  1. local myItem = {};
  2. myItem[0].myArmor = 56;
  3. myItem[0].myStamina = 10;
  4. myItem[0].myStrength = 21;
The second one will throw an error since myItem[0] results in nil, which then is indexed by the following lines. You need to assign a new table to myItem[0] in order to continue.

As far as the rest of the code, it's highly inefficient. Instead of taking values out of a returned table and sticking them in another one, you should just assign the returned table to a master table. Note you should put this code directly where the function is being called from instead of making a function out of it unless you need the code run from multiple places.
Lua Code:
  1. local ItemStats={};
  2. function UpdateItemSlot(slot)
  3.     local link=GetInventoryItemLink("player",slot);
  4.     if link then
  5.         ItemStats[slot]=GetItemStats(link);
  6.     elseif ItemStats[slot] then
  7.         ItemStats[slot]=nil;
  8.     end
  9. end
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
09-19-12, 09:27 PM   #8
Torhal
A Pyroguard Emberseer
 
Torhal's Avatar
AddOn Author - Click to view addons
Join Date: Aug 2008
Posts: 1,196
Also, in Lua, array-type tables begin at index 1 - not 0.
__________________
Whenever someone says "pls" because it's shorter than "please", I say "no" because it's shorter than "yes".

Author of NPCScan and many other AddOns.
  Reply With Quote
09-19-12, 10:08 PM   #9
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,860
Also also, what's best for storage depends on how you intend to access the data later.
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
09-20-12, 04:18 PM   #10
Kcir
A Defias Bandit
 
Kcir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2011
Posts: 3
Alright I guess I originally thought that it was better to have multiple smaller functions rather than one very large function... Thank you for clarifying that.

The second one will throw an error since myItem[0] results in nil, which then is indexed by the following lines. You need to assign a new table to myItem[0] in order to continue.
How would I assign a new table to myItem[0]?
Code:
myItem[0]={};
Something like that? Doesn't seem to make a difference from the first code I posted (still two tables)...

I'm sorry for my ignorance, most of the code I have is pieced together from a lot of short pieces of example code I made to see if it would work in the first place. I did the best I could to adapt the example code to the scope I was trying to accomplish.

I realize now I am in need of serious reworking on my addon.

Thanks,
Kcir
__________________
If practice makes perfect and nobodies perfect, why practice?
Because practice makes better.
  Reply With Quote
09-20-12, 04:48 PM   #11
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Yes, that's exactly how you create a table.

Code:
local myItem = {}
myItem[0] = {}
myItem[0].myArmor = 56
myItem[0].myStamina = 10
myItem[0].myStrength = 21
... produces this:

Code:
local myItem = {
    [0] = {
        myArmor = 56,
        myStamina = 10,
        myStrength = 21,
    },
}
However, I don't know why you are trying to assign a table to the 0 index... all of the index-related functions in Lua assume a starting index of 1, not 0, and there is no "equipment slot 0" (there used to be, but it was ammo, which hasn't been a real thing since Cataclysm launched).

If you're trying to keep track of how much of each stat each equipped item has, something like this will do it very simply:

Code:
local mystats = {}

local tempstats = {}
for slot = 1, 19 do
	local link = GetInventoryItemLink("player", slot)
	for stat, value in pairs(link, tempstats) do
		if not mystats[stat] then
			mystats[stat] = {}
		else
			wipe(mystats[stat])
		end
		mystats[stat][slot] = value
	end
	wipe(tempstats)
end
If you run that while wearing a chest with 52 stamina, a bracer with 31 stamina, and a cloak with 78 strength, you will end up with:

Code:
mystats = {
    Stamina = {
		5 = 52, -- 52 stamina on your chest item
		9 = 31, -- 31 stamina on your wrist item
	},
	Strength = {
		15 = 78, -- 78 strength on your back item
	},
}
__________________
Retired author of too many addons.
Message me if you're interested in taking over one of my addons.
Don’t message me about addon bugs or programming questions.
  Reply With Quote
09-20-12, 05:35 PM   #12
Farmbuyer
A Cyclonian
AddOn Author - Click to view addons
Join Date: Feb 2006
Posts: 43
Originally Posted by myrroddin View Post
Sorry for jumping in, but the question about table.wipe/wipe made me think to ask if that is the most efficient method to clear a table. I'm assuming the larger the table, the better the example.
Lua Code:
  1. local someTable = {} -- can be any size up to Lua's maximum, but assume there is a LOT of data
  2. wipe(someTable)
  3.  
  4. -- or this method
  5. local someTable = {} -- same parameters
  6. for i = 1, #someTable do
  7.     someTable[i] = nil
  8. end
I have heard the second method is faster and is better from a CPU side.
Originally Posted by SDPhantom View Post
From a coding standpoint, I'm not entirely sure if the code as written in the second method would work as intended as the length would change to 0 when you wipe the first element. It is undocumented on whether the length would be resolved on first encounter or on every loop check
wat?

The behavior of numeric 'for' loop conditions is completely documented, and always has been. They are evaluated once and once only, before the loop starts. If someTable contained only numeric keys in a simple list, then those two blocks of code are completely equivalent. The dangers are as Phanx pointed out: non-numeric keys won't be removed, and if you have "holes" in your list, then #someTable won't give the answer you expect. In any case, the 'for' loop doesn't keep re-evaluating its boundaries.

Internally, wipe() does the same iteration that pairs() would do. (More precisely, pairs() uses next(), and wipe uses the internal code that next() itself uses.) The question to be asking isn't how best to clear the table, but whether you should clear&reuse a table versus abandoning it to the garbage collector and creating a new one. There's a ton of information out there about that tradeoff, including on the wowinterface and wowace forums. Most of the time, reuse is better, but not always. (Often you don't have a choice; if your table needs to be referred to by other data afterwards, then you must clear the existing table.)
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Nested tables vs multiple tables


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off