Таблицы Corona SDK

balls = {}
createBall()

function checkLocation()
    for i,v in pairs(balls) do --loops through all the balls
        if v.x>320 or v.x<-50 or v.y<-30 then --Removes balls outside of the screen
            v:removeSelf()
            balls[i]=nil
            print "gone"
        end 
    end
end

function shoot(x, y)
    if balls[#balls].inAir==false then --Checks if the last made ball is already shot
        balls[#balls]:setLinearVelocity(-x,-y) --sets the velocity for the last made ball
        balls[#balls].inAir=true; -- is shot
    end
    timer.performWithDelay( 1000, createBall ) -- Creates a new ball after 1 second
end

function createBall()
    boll = {};
    boll = display.newCircle( 160, 400, 20 )
    boll.x, boll.y = 160, 400
    boll:setFillColor(255,0,0)
    physics.addBody( boll, { density=1.0, friction=0.3, bounce=0.3, radius=25} )
    boll:setLinearVelocity(0,0)
    boll.inAir=false
    balls[#balls+1] = boll; -- adds the new ball to the balls table
    timer.performWithDelay( 1000, shoot ) -- shoots the ball after 1 second
end

Моя программа работает на 3 бала. Они создаются, выстреливаются и удаляются, но он не может выстрелить 4-м шаром. Программа работает, если я удаляю всю функцию checkLocation, но я хочу удалить шары. Что я делаю неправильно? Может быть, что-то с balls[#balls] не так, но для меня это должно означать, что он выбирает последний сделанный шар.


person user3055331    schedule 02.12.2013    source источник


Ответы (2)


Прежде всего, если ваша таблица balls представляет собой таблицу, подобную массиву, или вы хотите выполнить итерацию только по части массива, вам лучше использовать ipairs или стандартный for i. pairs перебирает все индексы таблицы, включая нечисловые например, строковые ключи.

Не говоря уже о том, что pairs (через next) также выполняет итерацию в неопределенном порядке, тогда как ipairs всегда будет выполнять итерацию предсказуемым образом от 1 до первого целочисленного ключа, отсутствующего в таблице. Обратите внимание, что если вы используете стандартный цикл for i с оператором длины #t< /a>, это может быть немного по-другому, если ваша таблица не является последовательной.

Если не задан метаметод __len, длина таблицы t определяется только в том случае, если таблица представляет собой последовательность, то есть набор ее положительных числовых ключей равен {1..n} для некоторого целого числа n. В этом случае n — его длина.


Во-вторых, установка числовых индексов таблицы, подобной массиву, оставит вашу таблицу с дырами.

local t = {1, 2, 3}
t[2] = nil
-- t is {1, nil, 3}, not {1, 3} as one might expect.

Вам нужно будет использовать table.remove, если вы хотите сохранить последовательная согласованность вашей таблицы.

local t = {1, 2, 3}
table.remove(t, 2)
-- t is {1, 3}

И последнее, но не менее важное — это менее очевидный и менее интуитивный недостаток в вашем подходе к удалению элементов.

local t = {2, 3, 5, 2, 1}
for i=1, #t do
    if t[i] % 2 == 1 then  -- If value is odd,
        table.remove(t, i) -- remove it.
    end
end
-- t is {2, 3, 2}, but that can't be right.

Вы удаляете элементы из массива, перебирая их в прямом порядке. Это неизбежно приведет к тому, что вы пропустите каждый элемент после удаленного, возможно, до точки, где вы перебираете внешнюю часть массива.

Если вы хотите удалить элементы из массива по мере его повторения, вы должны сделать это назад. Если по какой-то причине вы не можете выполнить итерацию по массиву в обратном направлении, вам придется сохранить список индексов, чтобы удалить их позже, и выполнить итерацию по ним в обратном порядке.

В этот момент вы можете спросить: «Почему назад?». Проще говоря, это потому, что цикл не обновляет длину или не сохраняет итератор таким же, как размер таблицы уменьшается, что вызывает пропуск. Если вы пойдете назад, вы сохраните структуру, в которой обрабатывается итерация. Длину не нужно обновлять, поскольку теперь она является начальным значением, а не конечным условием. Итератор не нужно «сдерживать» или удерживать от шага, поскольку перед ним всегда будет больше элементов из-за того, что он движется назад.

local t = {2, 3, 5, 2, 1}
for i=#t, 1, -1 do -- Start at the end and go backwards.
    if t[i] % 2 == 1 then
        table.remove(t, i)
    end
end
-- t is {2, 2}, as expected.

В заключение (версия tl;dr)

  • Используйте ipairs вместо pairs для массивов, если вы предпочитаете использовать итераторы вместо цикла for i.
  • Используйте table.remove для сохранения последовательности массива.
  • Используйте обратные циклы for i при удалении элементов из массивов.
person Ryan Stein    schedule 04.12.2013
comment
Лучший ответ относительно таблиц lua. Поздравляем. Это мне очень помогает. - person Marcelo Vitoria; 16.02.2014

У вас есть массив шаров, вы не можете сделать balls[i]=nil, вам нужно сделать table.remove(balls, i), иначе ваш массив будет иметь нулевое значение в середине и перестанет работать должным образом, использование table.remove удаляет элемент и сдвигает оставшиеся элементы.

person Bruno Domingues    schedule 04.12.2013
comment
Этот ответ верен лишь частично. Вы не упомянули, как удаление элементов из таблицы при обходе ее с помощью pairs приведет к пропуску. - person Ryan Stein; 04.12.2013
comment
@RyanStein Конечно, то, что вы пишете, просто неправильно, см. справочник по Lua. manual: *Поведение next не определено, если во время обхода вы присваиваете какое-либо значение несуществующему полю в таблице. Однако вы можете изменить существующие поля. В частности, вы можете очистить существующие поля. * - person dualed; 04.12.2013
comment
Вы можете очистить существующие поля. Очистить — это не то же самое, что удалить в контексте последовательности/массива. Пожалуйста, попробуйте эту небольшую демонстрацию, и вы увидите, что один из 5 не будет удален, поэтому он будет пропущен. t = {2, 3, 5, 5, 2, 1};for k,v in pairs(t) do if v % 2 == 1 then table.remove(t,k) end end;print(table.concat(t, ',')) - person Ryan Stein; 04.12.2013