I'm working on a leveling and experience system in lua and I was wondering how could I optimize and maybe transform these elseifs in a lua table. The idea is to check if mylevel is less or equal to a defined maxlevel to use different multipliers depending on your level
local mylevel = 1
local experience = 50
if mylevel and experience then
if mylevel <= 10 then
multiplier = 1
elseif mylevel <= 15 then
multiplier = 2
elseif mylevel <= 25 then
multiplier = 3
elseif mylevel <= 35 then
multiplier = 4
elseif mylevel <= 45 then
multiplier = 5
elseif mylevel <= 80 then
multiplier = 6
elseif mylevel <= 100 then
multiplier = 7
elseif mylevel > 100 then
multiplier = 8
end
end
First off: Do you really need to optimize? First benchmark and verify whether there is a performance issue. Often asymptotically suboptimal constructs are in practice optimal or at least "good enough" for small numbers due to overhead. Given your small elseif chain of about ten branches, this might very well be the case. I'd also imagine that (at least in simpler cases) LuaJIT might be able to perform optimizations to achieve sublinear asymptotic runtime (f.E. I'd expect a switch-like construct with many equality comparisons to become something like a jump table when the code is JIT compiled; it probably won't nest comparisons intelligently though, esp. since there can be a metamethod involved and order matters). There probably is no need for optimization here.
If you want to switch to a data-driven design for maintenance reasons, that's a different matter. But even there it might be argued that the code in its current form is straightforward and readable.
Bottom line: I see no need to touch your code (yet). You can very well keep it simple as-is; only if you are to add (many) more levels is when you should consider "proper" data structures.
Note also that you can replace your last elseif
with simply an else
, assuming <=
is sane.
Gets the job done in asymptoptically logarithmic time. Has significant overhead though.
It is indeed preferable to store levels and their corresponding multipliers out of band:
local levels = {10, 15, 25, 35, 45, 80, 100}
local multipliers = {1, 2, 3, 4, 5, 6, 7, 8}
then binary search on levels and use the returned insertion index to find the corresponding multiplier.
The reduced overhead is twofold:
I conjure that it would be most efficient to express the binary search directly through nested if
s. That way you get rid of just about any and all indirection.
This is how may write it given your if
-elseif
-else
chain:
if mylevel <= 35 then
if mylevel <= 15 then
if mylevel <= 10 then
multiplier = 1
else
multiplier = 2
end
else
if mylevel <= 25 then
multiplier = 3
else
multiplier = 4
end
else
if mylevel <= 80 then
if mylevel <= 45 then
multiplier = 5
else
multiplier = 6
end
else
if mylevel <= 100 then
multiplier = 7
else
multiplier = 8
end
end
This is of course not particulary maintainable: It is in fact hardcoding a (reasonably) balanced binary search tree into your code. You could however write yourself some code to generate this code.
The alternative to a binary search is a simple table which maps from levels to multipliers. Since this is practically an array, access will be optimal - O(1). However you'll need to waste O(n) memory where n is the number of levels up to the upper bound after which you just return a constant max - 100 in your case. This might look as follows:
local multipliers = {}
local idx = 0
local function add_multipliers(levels, mult)
for _ = 1, levels do
idx = idx + 1
multipliers[idx] = mult
end
end
add_multipliers(10, 1) -- first ten levels have multiplier 1, and so on...
add_multipliers(5, 2) -- next five levels have 2
add_multipliers(10, 3)
add_multipliers(10, 4)
add_multipliers(10, 5)
add_multipliers(35, 6)
add_multipliers(20, 7)
now your function becomes trivial:
local function get_multiplier(level)
if level > 100 then return 8 end
return multipliers[level]
end
or, as Nifim points out, simply
local function get_multiplier(level)
return multipliers[level] or 8
end
since multipliers[level]
will be nil
when level
is out of bounds, thus 8
conceptually is a "default" value (small caveat: this might mask mistakes such as passing level = nil
or level = 0
; it would return 8
for those, whereas the previous solution would error or return nil
)
This is very likely to be both simpler and faster than a binary search. Use this if your numbers are small, as in your example.