How do I loop through a 2D array in lua

7.1k views Asked by At

I am attempting to make a WoW addon and am saving some guild data in a 2D array. I have successfully saved the data to the array but am having trouble getting it back out.

local playerName = UnitName("player");
ChatFrame1:AddMessage('Hi my name is: ' .. playerName);

local guildMembers = {}

local totalMembers, onlineMembers = GetNumGuildMembers();
local visibleMembers = onlineMembers;
local guildMembers = {}

if ( GetGuildRosterShowOffline() ) then
   visibleMembers = totalMembers;
end

for index=1, visibleMembers do
   local name = GetGuildRosterInfo(index);
   local weeklyXP = GetGuildRosterContribution(index);
   guildMembers[index] = {}
   guildMembers[index][1] = name;
   guildMembers[index][2] = weeklyXP;
   --DEFAULT_CHAT_FRAME:AddMessage('name: '..guildMembers[index][1]..' weeklyXP: '..guildMembers[index][2]);
end

for i, v in pairs(guildMembers) do
   for j, v2 in pairs(i) do
      print(i.. ': ' ..v.. ' xp: ' ..v2);
   end
end

Everything seems to work but for the last nested for loop. Just a note, my array may skip numbers. I have Google it but most of the questions asked knew how long their array was and I do not. Thanks for the help!

3

There are 3 answers

0
dr01d3k4 On

Try using a numerical for loop instead of a generic one.

-- The # operator gets the length of a table
for i = 1, #guildMembers, 1 do
    print(i.." - Name: "..guildMembers[i][1].."; XP: "..guildMembers[i][2]);
end

Or you could use a dictionary:

local guildMembers = {
    name1 = weeklyXP1;
    name2 = weeklyXP2;
};
for name, xp in pairs(guildMembers) do
    print(name..": "..xp);
end
0
Beeeaaar On

To loop through a 2D array, you can an inner and outer ipairs() iterator, or an 'index' in a regular for do loop for numerical indexes in order. You can also use pairs() with all index types like numbers and strings, but will be in an 'undefined' order.

You use both fine except that farther down in your code, you have a pairs vs. ipairs issue basically assuming you wanted it 'in order', along with other signs of frustration in the code :). This confusion is very common. You also tried to iterate over the inner array incorrectly, you can just access them directly be index. You can do for j,v2 in ipairs(v) do which is pretty much an idiom for this kind of thing.

In Lua tables 'contain' both array like indexed data, and name/value pair data, and they are separate internally and have 'sometimes' different semantics. In the case of 'pairs' iterators, they are different. :) The ipairs() operates on the 'indexed' data, and pairs() iterates the name / value data. When you added rows using bob[i]=fred; you were adding to the 'indexed' part of the table, due to the various rules that govern this.

If wanted just the inner elements, treat the outer (and inner) like any table:

for i, v in ipairs(guildMembers) do print(v[1] .. ':' ..v[2]) end

Otherwise get an inner element, and just rinse and repeat:

for i,v in ipairs(x) do for j,v2 in ipairs(v) do print(v2) end end

Here is a mockup that can be tested by itself:

local guildMembers = {}
local visibleMembers = 10;

if visibleMembers then

    -- building using 'for'
    for index = 1, visibleMembers do
        local name = "name" .. index --GetGuildRosterInfo(index);
        local weeklyXP = index * 12345 --GetGuildRosterContribution(index);
        guildMembers[index] = {}
        guildMembers[index][1] = name;
        guildMembers[index][2] = weeklyXP;
    end

    -- reading using 'ipairs'
    for i, v in ipairs(guildMembers) do
        print(i.. ': ' ..v[1].. ' xp: ' ..v[2]);
    end
    -- or
    for i, v in ipairs(guildMembers) do
        for j, v2 in ipairs(v)    -- takes the 'object' in 'v' and iterates sub
            print(i..' '..j.. ': ' ..v2.. 'name or xp');
        end
    end

end

Here is your code redone, which will probably work as is:

-- say my name
local playerName = UnitName("player");
ChatFrame1:AddMessage('Hi my name is: ' .. playerName);

-- locals
local guildMembers = {}

local totalMembers, onlineMembers = GetNumGuildMembers();
local visibleMembers = onlineMembers;

if thenGetGuildRosterShowOffline() then
    visibleMembers = totalMembers;
end

if visibleMembers then  
    -- build list
    for index=1, visibleMembers do
        local name = GetGuildRosterInfo(index);
        local weeklyXP = GetGuildRosterContribution(index);

        -- method 1   (ordered in likeliness of least to most expensive)
        --guildMembers[index] = { name, weeklyXP, }

        -- method 2
        --tinsert(guildMembers, { name, weeklyXP, })

        -- method 3
        --local item = { }; item[1] = name; item[2] = weeklyXP;
        --guildMembers[index] = item

        -- method 4 - original
        guildMembers[index] = {}
        guildMembers[index][1] = name;
        guildMembers[index][2] = weeklyXP;

        --DEFAULT_CHAT_FRAME:AddMessage('name: '..guildMembers[index][1]..
        --    ' weeklyXP: '..guildMembers[index][2]);
    end
    -- print
    for i, v in ipairs(guildMembers) do
        --for j, v2 in ipairs(i) do -- dont want loop if print in same line                     print(i.. ': ' ..v[1].. ' xp: ' ..v[2]);
        --end
    end
end
0
Oliver On

You should use pairs(v) in inner loop, not pairs(i), and I think you want ..j.. not ..v..:

   for j, v2 in pairs(v) do
      print(i.. ': ' ..j.. ' xp: ' ..v2);
   end

But you should use a map for the inner part, as in

guildMembers[index] = {name=name, xp=weeklyXP}

then inner loop becomes

   for j, v2 in pairs(v) do
      print(i.. ': ' ..j.. ' = ' ..v2);
   end