从Lua中的表中随机选择一个键

Randomly select a key from a table in Lua

我想使用可能的项目列表在Lua中随机填充一个网格,定义如下:

1
2
3
4
5
6
7
8
  -- Items
  items = {}
  items.glass = {}
  items.glass.color = colors.blue
  items.brick = {}
  items.brick.color = colors.red
  items.grass = {}
  items.grass.color = colors.green

因此,表的键是"玻璃","砖"和"草"。

如果无法通过数字索引寻址,如何随机选择这些键之一?


好吧,我有一个解决方法,但是我愿意接受任何更好的建议。

第一个解决方案包括拥有一个辅助表,该辅助表用作第一个表的索引:

1
item_index = {"grass","brick","glass"}

然后,我可以随机存储此表的键(board是将随机条目的值存储在item_index中的矩阵):

1
2
local index = math.random(1,3)
board[i][j] = item_index[index]

之后,我可以获得原始列表的详细信息,如下所示:

1
items[board[y][x]].color

我已决定的第二种解决方案是将定义的元素作为数组元素添加到原始表中:

1
2
3
4
5
6
7
8
9
10
11
  -- Items
  items = {}
  items.glass = {}
  items.glass.color = colors.blue
  table.insert(items, items.glass)   --- Add item as array item
  items.brick = {}
  items.brick.color = colors.red
  table.insert(items, items.brick)   --- Add item as array item
  items.grass = {}
  items.grass.color = colors.green
  table.insert(items, items.grass)   --- Add item as array item

然后,我可以使用索引直接处理元素:

1
2
  local index = math.random(1,3)
  board[i][j] = items[index]

而且可以直接检索它们,而无需其他查找:

1
  board[y][x].color


上面的答案假设您知道所有键是什么,而这不是我今天早些时候能做的。我的解决方案:

1
2
3
4
5
6
7
8
9
10
11
function table.randFrom( t )
    local choice ="F"
    local n = 0
    for i, o in pairs(t) do
        n = n + 1
        if math.random() < (1/n) then
            choice = o      
        end
    end
    return choice
end

说明:我们无法使用table.getn(t)来获取表的大小,因此我们会一直跟踪它。第一项将有1/1 = 1的机会被选中;第二个1/2 = 0.5,依此类推...

如果您扩展N个项目,则第N个项目将有1 / N的机会被选择。第一项将有1-(1/2)-(1/3)-(1/4)-...-(1 / N)机会不被替换(请记住,始终是第一次选择) 。该级数收敛到1-(N-1)/ N = 1 / N,这等于选择了最后一项的机会。

因此,数组中的每个项目都有相同的可能性被选择;它是均匀随机的。这也需要O(n)的时间运行,虽然不好,但是如果您不知道索引名,这是您可以做的最好的事情。


如果您的桌子不太大,可以随意休息一下。此方法假定您知道表中的条目数(如果表具有非数字键,则该值不等于#table值)。

因此,找到表的长度,然后在random(1, length(table))处中断,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local items = {} ....
items.grass.color = colors.green

local numitems = 0 -- find the size of the table
for k,v in pairs(items) do
    numitems = numitems + 1
end

local randval = math.random(1, numitems) -- get a random point

local randentry
local count = 0
for k,v in pairs(items) do
    count = count + 1
    if(count == randentry) then
        randentry = {key = k, val = v}
        break
    end
end

货物:您不必跟踪钥匙。它可以是任何表,您不需要维护它。
坏而丑陋的是:O(n)-两次线性传递,因此,如果您有大桌子,那根本不理想。


尽管您的第二种方法给出了简洁的语法,但我认为第一种方法更易于维护。我无法在这里进行测试,但我认为您可以同时获得两者的最佳效果,因此无法完成这项工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local items = {
    glass = {
        color = colors.blue,
    },
    brick = {
        color = colors.red,
    },
    grass = {
         color = colors.green,
    },
}
local item_index = {"grass","brick","glass"}
local index = math.random(1,3)
board[i][j] = items[item_index[index]]
print('color:', board[i][j].color)