如何有效地从lua表中删除nil值

我是lua的新手,也是编程的新手。我正尝试实现以下功能。

我有一个从中选择随机数的数字表。

myTable = {}

for i = 1 to 100 do
   table.insert(myTable, i)
end

local numberChosen = myTable[math.random(#myTable)]

到目前为止,都很好。下一次选择数字时,我希望该数字从表中删除。我知道lua不会删除值,它们作为空值保留。所以

table.remove(myTable, numberChosen)

不能用,因为如果值为空,我再次运行随机函数,就会得到 “bad argument #1 to 'random' (interval is empty)”

我已经尝试创建一个这样的函数:

function cleanTable(t)
    local cleanTable = {}
    for k, v in ipairs(t) do
        if v ~= nil then
            table.insert(cleanTable, v)
        end
    end
    return cleanTable
end

myTable = cleanTable(myTable)

但是它也行不通,因为随机函数返回相同的错误。有人能帮忙吗?

编辑-----------------------------------------------------------------------

我的表包含如下键和值:

local columns = {1,2,3,4,5,6,7,8,9,10,11,12}
local rows = {1,2,3,4,5,6,7,8,9,10,11,12}
local coupledNumbers = {}

for i = 1,#columns do
    for a = 1,#rows do
        local coupledNumber = i * a
        table.insert(coupledNumbers, i*a, coupledNumber)
    end
end

如何删除键和值?谢谢

点赞
用户7396148
用户7396148

当你定义 numberChosen 时有问题。当你定义

local numberChosen = myTable[math.random(#myTable)]

时,你错误地把 numberChosen 定义为表中某个任意索引处的值。

让我们来看一下你代码的几个循环

Loop: 1     numberChosen: 1     #myTable 100
Loop: 2     numberChosen: 57    #myTable 99
Loop: 3     numberChosen: 20    #myTable 98
Loop: 4     numberChosen: 82    #myTable 97
Loop: 5     numberChosen: 60    #myTable 96
Loop: 6     numberChosen: 48    #myTable 95
Loop: 7     numberChosen: 35    #myTable 94
Loop: 8     numberChosen: 91    #myTable 93
Loop: 9     numberChosen: 82    #myTable 92
Loop: 10    numberChosen: 74    #myTable 91
Loop: 11    numberChosen: 17    #myTable 90
Loop: 12    numberChosen: 86    #myTable 89
Loop: 13    numberChosen: 70    #myTable 88
Loop: 14    numberChosen: 49    #myTable 87
Loop: 15    numberChosen: 30    #myTable 86
Loop: 16    numberChosen: 3     #myTable 85
Loop: 17    numberChosen: 10    #myTable 84
Loop: 18    numberChosen: 38    #myTable 83
Loop: 19    numberChosen: 16    #myTable 82
Loop: 20    numberChosen: 17    #myTable 81
Loop: 21    numberChosen: 100   #myTable 80

请注意,循环21发生了什么,我们有了数字100,这是一个不能存在的索引。我们从列表中移除了20个项目,所以列表只有80个项目长。

当我们调用 math.random(#myTable) 时,我们将结果限制在我们表的范围内,但是当我们执行 myTable[math.random(#myTable)] 时,我们不再有这种信心。

当我们从表中删除值时,它会缩小,但表内的值不会改变,导致引用超出我们表边界的索引值增加。

因此,我们需要将 numberChosen 定义为 math.random(#myTable) 而不是 myTable[math.random(#myTable)],此外,为确保我们具有唯一的索引值,我已更改了用于构建数组的 for 循环:

local columns = {1,2,3,4,5,6,7,8,9,10,11,12}
local rows = {1,2,3,4,5,6,7,8,9,10,11,12}
local coupledNumbers = {}

print(coupledNumbers == {})

for i = 1, #columns do
    for a = 1, #rows do
        local index = ((i - 1) * #rows) + a
        coupledNumbers[index] = index
    end
end

for i = 1, 144 do
  local numberChosen = math.random(#coupledNumbers)
  print(i, numberChosen, coupledNumbers[numberChosen])
  table.remove(coupledNumbers, numberChosen)
end
2020-01-02 20:46:51
用户10126088
用户10126088

你可以使用键-值表格而不是数值表格。代码有点丑。

local columns = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
local rows = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
local coupledNumbers = {}

-- 和 `i = 1, 12` 一样,你甚至不需要表格 `columns` 和 `rows`,因为你不需要索引
for i = 1, #columns do
  for j = 1, #rows do
    local idx = i * j
    if not coupledNumbers[idx] then
      coupledNumbers[idx] = true
    end
  end
end

local function getRand(n)
  local res
  if next(coupledNumbers) ~= nil then
    repeat
      local r = math.random(n)
      if coupledNumbers[r] then
        res, coupledNumbers[r] = r, nil
      end
    until res
  end

  return res
end

print(getRand(144))
print(getRand(144))
print(getRand(144))
print(getRand(144))
print('---')

for i in pairs(coupledNumbers) do
 print(i)
end
2020-01-03 09:33:36
用户5373986
用户5373986

我倾向于采用生成一个棋盘(一个二维数组)的方式来解决这个问题,并在每次被击中时将每个元素设置为true。然后在选择每个命中时仅需检查该棋盘,如果它已经是true,那么它就是重击,如果是false,则为新的击中。

这是一开始的数组:

StartArray

这是100个随机击中后的结果:

EndArray

并不是所有的100个格子都被标记为true,因为其中很多都是重击,你可以从下面的控制台中看到:

ConsoleLog

你可以从“playGame”函数中的“boardHeight”和“boardWidth”修改棋盘大小,如果需要任何帮助将其集成,可以留言,我会帮助你。

local function getNewBoard(newHeight, newWidth)
    local newBoard = {}
    for i = 1, newHeight do
        newBoard[i] = {}
        for j = 1, newWidth do
            newBoard[i][j] = false
        end
    end
    return newBoard
end

local function playGame()
    local boardHeight = 10 --将其更改为所需高度
    local boardWidth = 10 --将其更改为所需宽度
    local gameBoard = getNewBoard(boardHeight, boardWidth)

    for _ = 1, 100 do
        --获取一个随机的击中,您可以将其更改为玩家输入
        local hitHeight = math.random(1, boardHeight)
        local hitWidth = math.random(1, boardWidth)

        if gameBoard[hitHeight][hitWidth] == true then --检查是否已经被击中
            print("[" .. hitHeight .. ":" .. hitWidth .. "]已经被击中了")
        else
            gameBoard[hitHeight][hitWidth] = true --标记为已被击中
            print("[" .. hitHeight .. ":" .. hitWidth .. "]刚刚被击中了")
        end
    end
end

playGame()
2020-01-03 15:55:36
用户936986
用户936986

我知道 Lua 不会删除值,它们会作为 nil 存在。

不,它并不是这样工作的。根据文档,它是这样工作的:

table.remove 函数会从给定的位置中删除并返回一个元素,并将其他元素下移以关闭空间并减少数组的大小。

-- https://www.lua.org/pil/19.2.html.

你的问题在于:

local randomPosition = math.random(#myTable) -- 获取随机位置
local numberChosen = myTable[randomPosition] -- 从该位置获取数值
table.remove(myTable, numberChosen)          -- 尝试移除元素,但是实际上需要指定其位置,而不是某个在该位置找到的任意值。

你的逻辑第一次工作纯粹是巧合,只是因为你的数组恰好与其位置匹配。

你需要根据位置进行删除:table.remove(myTable, randomPosition)

你的第二个函数不仅没有用,而且没有意义。for in ipairs 永远不会返回 nil 值 - 它是数组的末尾,因此 for 循环只会关闭。

2022-11-16 04:59:14