WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   Lua/XML Help (https://www.wowinterface.com/forums/forumdisplay.php?f=16)
-   -   3 LUA questions (array, vars scope, mob health) (https://www.wowinterface.com/forums/showthread.php?t=43024)

Animor 03-15-12 05:02 AM

3 LUA questions (array, vars scope, mob health)
 
Hello,

I have some LUA related questions I hope someone here will be able to answer.

1) What is the best way to search an array?
Code:

for _, value in pairs(myArray) do
  -- use value
end

or
Code:

for i = 1, #myArray do
  -- use myArray[i]
end

or perhaps another way?

2) Let's say I have this code:
Code:

function xxx ()
  local counter = ...
 
  for counter = 1, 3, 1 do
      print(counter)
  end

  -- use counter
end

Is "counter" of the for loop local only for the for loop scope? Or will the loop alter the "counter" declared at the beginning of the function?

3) I want to track boss health. for that I use "boss1" .. "boss4" which relate to the 4 boss frames on the right side of the screen. for example:
Code:

UnitHealth("boss1")
The problem is that at a certain phase some adds might fill those boss frames and "push out" the original boss. This happens for example on Yor'sahj, when 4 Globules appear and "take" all 4 boss frames. How can I still get Yor'sahj health in such scenario?
Is there a way to get unit health through the unit name for example? Any other solution?

Thanks in advance!

haste 03-15-12 06:03 AM

1) Depends on the content of your array. If it is an array with unsorted data, then the second solution should be slightly better, but not by much. If it's sorted then doing a binary search would probably be better.

2) The first counter is local to the scope of xxx(), while the for loops counter is only local to the for loop. quick example:
Lua Code:
  1. Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
  2. > do
  3. >> local c = 0
  4. >> for c=1, 3 do print(c) end
  5. >> print(c)
  6. >> end
  7. 1
  8. 2
  9. 3
  10. 0

3) You can do it by tracking changes to the HP through the combat log or through someones target. No directly easy way to track such when there's no valid unit id to track.

Animor 03-15-12 07:11 AM

Thanks!

Can you elaborate a bit about the impact of unsorted array on those two search methods?
btw, I also saw usage of "ipairs" instead of "pairs". Which one is better for going over array elements?

Thanks.

Seerah 03-15-12 10:53 AM

It means by either having numerical keys or a dictionary-style lookup table. (ie, whether you name the keys or leave them as numbers)

Phanx 03-16-12 02:54 AM

Array = indexed table = table where the keys are sequential integers beginning with 1.

Dictionary = hash table = table where the keys are anything else.

Unlike some programming languages, Lua uses the same data structure -- the table -- for both paradigms.

This is an array:
Code:

local t = {
        "apple",
        "banana",
        "cherry",
}

This is also an array, and is identical to the above:
Code:

local t = {
        [1] = "apple",
        [2] = "banana",
        [3] = "cherry",
}

The ipairs function will operate identically on both tables:
Code:

for i, v in ipairs(t) do print(i, v) end
Quote:

1 apple
2 banana
3 cherry
However, if the indices are not sequential, like this:
Code:

local t = {
        [1] = "apple",
        [2] = "banana",
        [3] = "cherry",
        [7] = "grape",
        [13] = "mango",
}

Then ipairs will only iterate over the indices that begin at 1 and increment sequentially:
Code:

for i, v in ipairs(t) do print(i, v) end
Quote:

1 apple
2 banana
3 cherry
Basically, it will act as if the other values aren't in the table.

You can also use pairs on an array, and it will return all the values, including the ones that ipairs skips because they aren't sequential, but it returns them in an undefined order, which basically means random:
Code:

for k, v in ipairs(t) do print(k, v) end
Quote:

3 cherry
13 mango
7 grape
1 apple
2 banana
You can also mix tables, like this:
Code:

local t = {
        "apple",
        "banana",
        [3] = "cherry",
        [7] = "grape",
        ["foo"] = "bar",
        ["num"] = 5,
}

In this case, ipairs will still iterate over the sequential indexed values, beginning with 1:
Code:

for i, v in ipairs(t) do print(i, v) end
Quote:

1 apple
2 banana
3 cherry
... and pairs will iterate over all of the key/value pairs, including the indexed ones (keys are sequential integers counting up from 1), in undefined order:
Code:

for k, v in ipairs(t) do print(k, v) end
Quote:

foo bar
2 banana
3 cherry
num 5
7 grape
1 apple
If you are using an array, it is slightly faster to avoid ipairs and simply iterate through the table this way:

Code:

for i = 1, #t do print(i, t[i]) end
Quote:

1 apple
2 banana
3 cherry
... but it's not really significant, so if you feel that ipairs is easier for you as a programmer to read and understand, feel free to use it.

As for which type of table structure you use, that depends on what kind of data you want to store, and how you want to use it. Without knowing that, I can't make any useful suggestions.

Animor 03-16-12 05:24 AM

Phanx,
Thank you for your thorough explanation!

Why is it faster to avoid ipairs and simply iterate through the array, as you wrote?

Ketho 03-16-12 05:53 AM

This thread might also be of help
http://forums.wowace.com/showthread.php?t=19541

Quote:

Originally Posted by Animor (Post 253975)
Why is it faster to avoid ipairs and simply iterate through the array, as you wrote?

Quote:

The first method (iterating through the array), because it is a table lookup only. The second method (ipairs) calls a function which basically does the same table lookup and returns the result.

Quote:

Originally Posted by Phanx (Post 253969)
If you are using an array, it is slightly faster to avoid ipairs and simply iterate through the table this way:

... but it's not really significant, so if you feel that ipairs is easier for you as a programmer to read and understand, feel free to use it.

I totally agree with this :)

and maybe someone is going to pop up with some benchmarks

Mikord 03-16-12 11:39 AM

Quote:

Originally Posted by Animor (Post 253975)
Why is it faster to avoid ipairs and simply iterate through the array, as you wrote?

The simplest answer is that ipairs has overhead associated with it since it is an iterator function. The example using the # operator, on the other hand is, just looping from 1 to the number of elements in the array.


Here are some very basic benchmark results between an empty loop iterating over an array with 10,000,000 entries using ipairs versus the # operator. The code is at the bottom of the post:

Code:

Generating array with 10000000 entries...
Iterating through 10000000 entries with # operator...
elapsed time: 0.09
Iterating through 10000000 entries with ipairs...
elapsed time: 1.34


Lua Code:
  1. local NUM_ITERATIONS = 10000000
  2.  
  3. print(string.format("Generating array with %d entries...", NUM_ITERATIONS))
  4. local array = {}
  5. for i = 1, NUM_ITERATIONS do
  6.     array[i] = i
  7. end
  8.  
  9. print(string.format("Iterating through %d entries with # operator...", #array))
  10. local start = os.clock()
  11. for i = 1, #array do
  12. end
  13. print(string.format("elapsed time: %.2f", os.clock() - start))
  14.  
  15. print(string.format("Iterating through %d entries with ipairs...", #array))
  16. local start = os.clock()
  17. for k, v in ipairs(array) do
  18. end
  19. print(string.format("elapsed time: %.2f", os.clock() - start))

Animor 03-16-12 11:52 AM

Very interesting, thank you for that benchmark!

Phanx 03-17-12 02:22 AM

Quote:

Originally Posted by Mikord (Post 253992)
The simplest answer is that ipairs ... is an ... function.

Fixed. Calling functions is basically the slowest thing you can do in Lua, so you should do it as little as possible. In the same vein, if you ever find yourself writing something like this:

Code:

local function DoAnotherThing()
    -- do another thing here
end

local function DoAThing()
    -- do a thing here
    if something then
        DoAnotherThing()
    end
end

DoAThing()

... ask yourself if the code inside the DoAnotherThing() function will ever actually be called from anywhere else. If the answer is "no", then you should just move that code into the DoAThing() function.

Animor 03-17-12 03:41 AM

Then why in the wow programming book they wrote that
Code:

table.insert(tbl, "new element")
is better than
Code:

tbl[#tbl + 1] = "new element"
They wrote about the second method:
Quote:

This is a really tedious and error-prone way to do something relatively simple.

Torhal 03-17-12 03:43 AM

Because it's "tedious and error-prone". The second way is faster, though, and it's what I tend to use.

Phanx 03-17-12 07:42 PM

The most efficient code isn't always the most readable code, so if you find a particular construct difficult to read and understand, you might choose to use code that's slightly slower or uses slightly more memory to make the code easier for you to write and maintain.

Generally, the difference between things like:
Code:

for i, v in ipairs(tbl) do
    -- do something with v
end

vs
Code:

for i = 1, #tbl do
    local v = tbl[i]
    -- do something with v
end

or
Code:

table.insert(tbl, "lolcats")
vs
Code:

tbl[#tbl+1] = "lolcats"
is too insignificant to be noticed in actual usage, so you should use whichever is easier for you to work with.

The exception would be if you're using it somewhere where speed really matters, like inside an OnUpdate script or in response to an event that fires very frequently like COMBAT_LOG_EVENT_UNFILTERED; in these kinds of places, you should always use the faster code.


All times are GMT -6. The time now is 09:39 PM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI