dynamic tables or arrays

2019-09-08 11:56发布

问题:

I have a function that is meant to dynamically change the size of a 3D Array or table but it keeps breaking saying that it return value nil because it is out of bounds. Here is the code for it:

function resize()       
    temp = { }
    for h=1, height do
        table.insert( temp , { } )
        for y=1, length do
            table.insert ( temp[h], { } )
            for x=1, width do
                num = 16                
                if #blocks <= height then
                    if #blocks[h] <= length then
                        if #blocks[h][y] <= width then
                            num = blocks[h][y][x]
                        end
                    end
                end         
                table.insert( temp[h][y] , num )
            end
        end
    end 
    blocks = temp
end

I know it's not very well commented but the idea is that it creates a new table with the changed dimensions, and then superimpose the data of blocks over the new table and finally overwriting blocks with the new temp table.

The length width and height are changed by one either incremented or decremented but only one at a time.

I'm not sure if I explained it clearly enough, if not let me know and I'll try explain in more detail.

Thanks all, James

回答1:

I think that the bug lies in your if statements. You should sizes of blocks against h, y and x, not height and length and width.

As a side note, do not use table.insert when you can substitute it with temp[h] = {}. It's faster. Also, try to use locals for temp storage.



回答2:

Your specific error (probably)

You do not test against nil values. Any non-initialized table (= array in this case) members are nil by definition. And comparing nil with a number will generate an error:

lua: attempt to compare nil with number

However, as you seem unable to provide the actual error message, this is only a guess. Do not take me wrong these are errors in your code, but there could be something else wrong that I overlooked. Anyway here are some comments along with your code to show you what happens

if #blocks <= height then              -- say #blocks is 3, height is 4
    if #blocks[h] <= length then       -- E: in iteration 4 blocks[h] is nil
        if #blocks[h][y] <= width then -- E: in it. 3,4 blocks[h][y] is nil
            num = blocks[h][y][x]
        end
    end
end

You would have to test against nil on every level first, like

if blocks[h] and blocks[h][y] and blocks[h][y][x] and
       #blocks[h]<=height and #blocks[h][y]<=height and #blocks[h][y][x]<=height
    num = blocks[h][y][x]
end

General programming mistakes

blocks, length, width and height seem to be parameters for your function but are not in its header, so I suppose you set them externally before calling the function? This is certainly not good practice.

temp and num should be declared local.

Alternative

You can make the data-structure more intelligent, for example, if you put your 3D array in a flattened table and add a __call metamethod like this

function newMdArray(X, Y, Z)
    local MT = { __call = function(t, x, y, z, v)
        if x>X or y>Y or z>Z or x<1 or y<1 or z<1 then return nil end
        local k = x + X*(y-1) + X*Y*(z-1);
        if v ~= nil then t[k] = v; end
        return t[k]
    end };
    return setmetatable({}, MT);
end

Then this is all you have to do to make a resized copy of it:

function resizeMdArray(array, X, Y, Z)
    local r = newMdArray(X, Y, Z);
    for x=1, X do
        for y=1, Y do
            for z=1, Z do
                r(x, y, z, array(x, y, z) or 16);
            end
        end
    end
    return r;
end

A nice bonus is, since this data structure flattens the 3D array into a 1D array, if you only want to copy the data, you can do so by simply accessing it as a table and copying each element:

for i=1, X*Y*Z do
    new[i] = old[i]
end

Of course you could do the very same with a "real" (hidden) 3-D array in the background saving you the arithmetic calculations, however then you would have to test for empty values all the time to prevent nil errors.



回答3:

Well I'm not sure if this is the best way to do it, but it does work.

function resize()       
temp = { } -- temp table

-- inserting all the height levels
for h=1, height do table.insert( temp , { } ) end

-- inserting all the lengths
for h=1, height do
    for l=1, length do table.insert( temp[h], { } ) end
end

-- inserting all the width and defaulting them to 0
for h=1, height do
    for l=1, length do
        for w=1, width do table.insert( temp[h][l] , 0 ) end
    end
end

-- if the canvas size is increasing
if #blocks <= height then
    if #blocks[1] <= length then
        if #blocks[1][1] <= width then
            for h=1, #blocks do
                for l=1, #blocks[1] do
                    for w=1, #blocks[1][1] do
                        -- fill in data from blocks
                        temp[h][l][w] = blocks[h][l][w]
                    end
                end
            end
        end
    end
end

--if the canvas size is decreasing
if #blocks >= height then
    if #blocks[1] >= length then
        if #blocks[1][1] >= width then
            for h=1, #temp do
                for l=1, #temp[1] do
                    for w=1, #temp[1][1] do
                        -- fill in data from blocks but not the last value
                        temp[h][l][w] = blocks[h][l][w]
                    end
                end
            end
        end
    end
end

-- overwrite blocks with the new dimensions
blocks = temp