Thread Tools Display Modes
03-30-13, 02:05 AM   #1
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
Table Iteration

Good Morning,

I was curious about how I may go about ensuring a table of mine is iterated over in a certain order (simply, top to bottom.)

I tried to use the ipairs() function for my loop, and it didn't work; and using pairs(), it reads my table in a rather erratic fashion.

My table utilizes the key and value as information, so I can't use the key as an index. The next() function seems to produce the same results as pairs().

Thanks!
  Reply With Quote
03-30-13, 03:20 AM   #2
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
The order in which key/value pairs are returned by pairs is undefined, so, basically random. next works the same way. The only way to directly walk through a table in a specific order is to use ipairs.

There are some workarounds, but they all involve creating an extra table with only the keys from the original table as indexed values, sorting it somehow, and then using ipairs on the extra table and value lookups on the original table, eg:

Code:
local t = {
    ["cow"] = "moo",
    ["pig"] = "oink",
    ["other"] = "?",
}

local order = { "cow", "pig", "other" }

for i, k in ipairs(order) do
    local v = t[k]
    -- do something with v
end
The other option would be to switch your table to an indexed table with subtables:

Code:
local t = {
    { "cow", "moo" },
    { "pig", "oink" },
    { "other", "?" },
}
Then you can sort it however you like, and use ipairs on it. However, you can't do direct value lookups anymore.

Without knowing what you're actually doing, it's hard to say what the best method will be.
__________________
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
03-30-13, 07:44 AM   #3
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
Hey Phanx,

Thanks for the reply.

What I am trying to accomplish is to have two tables of relevant buffs and debuffs, and their respective modification to healing. An example would be:

Lua Code:
  1. -- ["Spell ID"] = percent
  2. local generalHealingIncreases = {
  3.     [47788] = 60,   -- Guardian Spirit
  4.     [55233] = 25    -- Vampiric Blood; needs to be adjusted for glyph.
  5. }

The way healing modifications are handled is multiplicative, from highest to lowest value. What I aim to do is sort the table from highest to lowest by hand, and by having my loop iterate over it in descending order, I can ensure the appropriate buff is calculated first.

That being said, I need to be able to modify the values through a look-up of the keys (to reflect glyphs); and the table would then need to be re-sorted to reflect the new values.

I am not sure that I like the idea of two tables. Is this a situation where metatables would be useful? I was poking around some Lua websites and there was a method utilizing metatables to emulate the next() function, but I wasn't too sure it was what I was looking for.

Thanks for your time and help (as usual ).

Last edited by Clamsoda : 03-30-13 at 09:27 AM.
  Reply With Quote
03-30-13, 11:49 AM   #4
Sharparam
A Flamescale Wyrmkin
 
Sharparam's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2011
Posts: 102
If your code is not performance critical, it may be easier to have a table of tables instead, something like:

lua Code:
  1. local buffs = { -- Unsorted buffs table
  2.     {1234, 25},
  3.     {4321, 60}
  4. }
  5.  
  6. for i, v in ipairs(buffs) do
  7.     print(i, v[1], v[2])
  8. end
  9.  
  10. --[[ Output:
  11. 1       1234    25
  12. 2       4321    60
  13. ]]
  14.  
  15. local function compare(a, b)
  16.     return a[2] > b[2]
  17. end
  18.  
  19. table.sort(buffs, compare)
  20.  
  21. for i, v in ipairs(buffs) do
  22.     print(i, v[1], v[2])
  23. end
  24.  
  25. --[[ Output:
  26. 1       4321    60
  27. 2       1234    25
  28. ]]
  29.  
  30. -- Now to find a specific buff ID and get the healing value, you could use something like:
  31.  
  32. local function getHealing(id)
  33.     for _, v in ipairs(buffs) do
  34.         if v[1] == id then return v[2] end
  35.     end
  36.     return nil -- Functions return nil by default, but whatever
  37. end
  38.  
  39. local healing = getHealing(1234) -- Returns 25
  40. healing = getHealing("this buff does not exist") -- Returns nil
  Reply With Quote
03-30-13, 11:56 AM   #5
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
Originally Posted by Clamsoda View Post
I am not sure that I like the idea of two tables. Is this a situation where metatables would be useful?
Using a metatable would still be using two tables.

Also why does the order matter if it's multiplicative?

Last edited by semlar : 03-30-13 at 12:01 PM.
  Reply With Quote
03-30-13, 12:20 PM   #6
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
Also why does the order matter if it's multiplicative?
Because the order in which you multiply, when dealing with percents, affects the out-come? Increasing an amount by 60%, then 40% yields a different answer than increasing an amount by 40%, then 60%.

I suppose you are correct about the two table approach, but the metatable could be universal for two seperate tables, yielding only 3 tables, instead of four? (Bare in mind I know nothing about metatables, I just saw it used as a solution to a problem similar to mine).

If your code is not performance critical...
I guess you could say that code is performance critical.

My approach was, and this is pretty theoretical, to sort a table of healing increases (and decreases in a separate table) by hand, and as a for-loop iterates over it (assuming it is in descending order), if a player has a buff in the table, that percentage will be applied to an amount, and the process continues. In the example posted above, if a player had BOTH Guardian Spirit and Vampiric Blood, a potential heal would be increased by 60%, then 25(or 40)%.

My approach could be horrid, but it seemed like the most simple approach, until I ran into the issue with my table being impossible to index, and impossible to iterate over with ipairs().

Thanks for the replies!

Edit:

Here was the script I was using (granted, it doesn't work in that it reads the table in any order, but it still works)
Lua Code:
  1. for k, v in pairs(generalHealingIncrease) do
  2.     if UnitAura("player", k) then
  3.         totalDamageAmount = addPercent(totalDamageAmount, v)
  4.     end
  5. end

Last edited by Clamsoda : 03-30-13 at 12:27 PM.
  Reply With Quote
03-30-13, 12:24 PM   #7
Sharparam
A Flamescale Wyrmkin
 
Sharparam's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2011
Posts: 102
Originally Posted by Clamsoda View Post
Because the order in which you multiply, when dealing with percents, affects the out-come? Increasing an amount by 60%, then 40% yields a different answer than increasing an amount by 40%, then 60%.
lua Code:
  1. local value = 100
  2. value = value * 1.6
  3. value = value * 1.4
  4. print(value) -- 224
  5. value = 100
  6. value = value * 1.4
  7. value = value * 1.6
  8. print(value) -- 224
  9.  
  10. -- Since (a * b) * c == a * b * c == a * (b * c) == a * k where k = (b * c)

Originally Posted by Clamsoda View Post
I guess you could say that code is performance critical.
Does it run very often (like in an OnUpdate script or CLEU)? If not, I think it would be pretty safe to say a few milliseconds extra won't matter too much. Or possibly you could cache the results somewhere(?) and use that in OnUpdate/CLEU.

Last edited by Sharparam : 03-30-13 at 12:28 PM.
  Reply With Quote
03-30-13, 12:28 PM   #8
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
Does it run very often (like in an OnUpdate script or CLEU)?
It uses both of those.
  Reply With Quote
03-30-13, 12:30 PM   #9
Sharparam
A Flamescale Wyrmkin
 
Sharparam's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2011
Posts: 102
I suppose you could run tests to see how much it actually affects performance. Coming up with some other work-around possibly involving metatables might still create a similar performance hit though, I'm not sure.

I'll let someone more experienced in the performance area comment on that :P
  Reply With Quote
03-30-13, 12:43 PM   #10
semlar
A Pyroguard Emberseer
 
semlar's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2007
Posts: 1,060
I just want to reiterate that the order does not actually matter when you're multiplying like this.

Try it yourself.
  Reply With Quote
03-30-13, 01:07 PM   #11
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
I just want to reiterate that the order does not actually matter when you're multiplying like this.
Let me go get my dunce cap; ugh I am so stupid. Thank you for teaching me 5th grade algebra again =/.

Thanks for the help guys, but it should work.
  Reply With Quote
03-30-13, 01:11 PM   #12
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,860
<-- math teacher

As F16Gaming pointed out in the last line of his example, the Associative Property of Multiplication - just like the Associative Property of Addition - proves that order does not matter when dealing with more than 2 numbers. (a*b)*c = a*(b*c) which also equals (a*c)*b. (When dealing with just 2 numbers, you are looking at the Commutative Property -> a*b = b*a.)

Hope this simplifies your process!

/edit: bah - you replied to this concept as I was typing...
__________________
"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
03-30-13, 01:23 PM   #13
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
I don't even want to talk about it

Thanks so much for the help. *Sobs in corner*
  Reply With Quote
03-30-13, 01:31 PM   #14
Nimhfree
A Frostmaul Preserver
AddOn Author - Click to view addons
Join Date: Aug 2006
Posts: 267
As a side issue, I am wondering why you are doing this. I wrote Simply Magical Healer ages ago and recorded all the modifications (buffs and debuffs) that affected healing. It was actually quite complicated back then since Blizzard seemed to do a variety of things. That addon lost its major use when Blizzard removed the ability to automatically target spell casts, and then when it removed spell ranks. I have no idea what Blizzard does with healing modifications now, but was wondering what your use would be for this information.
  Reply With Quote
03-30-13, 03:09 PM   #15
Clamsoda
A Frostmaul Preserver
Join Date: Nov 2011
Posts: 269
Hello Nihmfree,

In a nut-shell, I have a known amount of healing incoming, and I need to know what it will be after healing modifications (increases and decreases).

Rather than run over all of the player's buffs, I'd rather run over a list of buffs that I know the player will have.

Edited for grammar; not my day

Last edited by Clamsoda : 03-30-13 at 03:56 PM.
  Reply With Quote
03-31-13, 12:04 AM   #16
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I'd go with something like this:
Code:
local healBuffs = {
	[(GetSpellInfo(47788))] = 1.6,  -- Guardian Spirit
	[(GetSpellInfo(55233))] = 1.25, -- Vampiric Blood; needs to be adjusted for glyph
}

local healDebuffs = {
	[(GetSpellInfo(8680))] = 0.75, -- Wound Poison
}

local unitIncomingHeals = {}
local unitModifiedHeals = {}

local MyAddon = CreateFrame("Frame")
MyAddon:SetScript("OnEvent", function(self, event, ...) return self[event] and self[event](self, ...) end)
MyAddon:RegisterEvent("UNIT_INCOMING_HEALS")

function MyAddon:UNIT_AURA(unit)
	local amount = unitIncomingHeals[unit]
	if not amount then return end

	for buff, mult in pairs(healBuffs) do
		local exists, _, _, count = UnitBuff(unit, buff)
		if exists then
			amount = amount * (mult * (count or 1))
		end
	end
	for debuff, mult in pairs(healDebuffs) do
		local exists, _, _, count = UnitDebuff(unit, buff)
		if exists then
			amount = amount * (mult * (count or 1))
		end
	end

	if unitModifiedHeals == amount then return end
	unitModifiedHeals[unit] = amount
	
	print("New heal amount on", unit, "is", floor(amount))
end

function MyAddon:UNIT_HEAL_PREDICTION(unit)
	local amount = UnitGetIncomingHeals(unit)
	if amount and amount > 0 then
		if not next(unitIncomingHeals) then
			self:RegisterEvent("UNIT_AURA")
		end
		unitIncomingHeals[unit] = amount
		return self:UNIT_AURA(unit)
	else
		unitIncomingHeals[unit] = nil
		if not next(unitIncomingHeals) then
			self:UnregisterEvent("UNIT_AURA")
		end
	end
end
Also, if you want a spreadsheet of all currently known healing debuffs, including spellIDs, amounts, stacks, sources, etc. send me a PM with your email address.
__________________
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
03-31-13, 07:42 AM   #17
Sharparam
A Flamescale Wyrmkin
 
Sharparam's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2011
Posts: 102
For someone who often discourages people from sending stuff in PMs/giving out email... :D

Why don't you just put it up somewhere public and share with the world? :P I'm sure there are others who'd find a spreadsheet with that kind of info useful.
  Reply With Quote
03-31-13, 07:48 AM   #18
Rilgamon
Premium Member
 
Rilgamon's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Sep 2009
Posts: 822
First I had the same thought. But then I realized that would just create a place with old outdated informations we will have to fight later on
__________________
The cataclysm broke the world ... and the pandas could not fix it!
  Reply With Quote
03-31-13, 06:26 PM   #19
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I'm against people using PMs/email for getting answers to their questions, because we have an entire forum system set up for that. I'm not against using PMs/email for sharing files, because forum software isn't very good at being a file repository. Also, this particular forum doesn't allow ODS attachments.

But, here you go anyway. Remove the TXT extension.
Attached Files
File Type: txt HealingDebuffList.ods.txt (29.2 KB, 342 views)
__________________
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
03-31-13, 06:28 PM   #20
Sharparam
A Flamescale Wyrmkin
 
Sharparam's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2011
Posts: 102
Well there's Dropbox and all those other file sharing services that allow you to hotlink/share special links for your public files. (Not like those crappy file hosting websites with 30 second wait times, eww...)
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Table Iteration

Thread Tools
Display Modes

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