Pick random numbers without repetition

Hi all,

I have got a bit of a problem that I haven’t managed to solve in Lua so far.
I have a table, let’s say this one: items_table = {1, 2, 3, 4, 5}. Each time the player clicks, I would like to take a random number from this table. My problem: these numbers should not repeat but I cannot just remove the number from the table, I need the table intact. In python, I would append the number to another list, compare both lists and if the number is in the second list, pick another number. But there is no “is in” or “is not in” in Lua like there is in python.
How could I go about doing this in Lua? Any hint would be greatly appreciated.

Perhaps if the “other” container is a table (and not a list).
Then you can check if the return value is nil

local used = {}
-- v is the value from items_table
-- 1 is just some value to make it not nil
used[v] = 1 

is_used = used[v] ~= nil
3 Likes

Thank you @Mathias_Westerdahl, I have tried something similar, but yours is a better idea. Will try out immediately.

2 Likes

Update: Here once lived slightly odd code - now removed. Nice, short and much better code see in my post further below.

4 Likes

This is an old thread - but the code I posted leaves wanting and I don’t want folks to use dodgy code.

Here is a valid version - I want to thank @ross.grams for it.

--[[
pick a random number from a table
produce a non-repeating number each time
and when restarting, do not pick the last one that was picked before
]]


local numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local len_numbers = 10
local last_number


local function pick_random()
	local random_index, random_number

	if #numbers == 0 then 
		for i = 1, len_numbers do
			table.insert(numbers, i)
		end
	end

	repeat
		random_index = math.random(#numbers)
		random_number = numbers[random_index]
	until random_number ~= last_number

	table.remove(numbers, random_index)
	last_number = random_number
	
	return random_number
end


function init(self)
	msg.post(".", "acquire_input_focus")
	math.randomseed(os.time())
	math.random()
end


function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		local number_picked = pick_random()
		print(number_picked)
	end
end
5 Likes

I decided to tackle the problem myself. The following approach requires a separate list of indices but avoids too many calls to table.remove. Not sure if it is more performant though.

local function shuffle(list)
    local n = #list
    for i = 1, n - 1 do
        local k = math.random(i, n)
        list[i], list[k] = list[k], list[i]
    end
    return list
end

function new_random_picker(list)
    local n = #list
    local index_list = {}
    for i = 1, n do
        index_list[i] = i
    end
    shuffle(index_list)

    local pos = 0
    return function()
        pos = pos + 1
        local index = index_list[pos]
        if index then
            return list[index]
        else
            local last = list[index_list[n]]
            repeat
                shuffle(index_list)
            until last ~= list[index_list[1]]
            pos = 1
            return list[index_list[1]]
        end
    end
end

Could be used like this:

local numbers = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }
local random_picker = new_random_picker(numbers)

for i = 1, 20 do
	print(random_picker())
end
2 Likes

Here is a variation using a closure:

local function setRandList()
   local randList,one ={}
   return function ()
         if 0 == #randList then
           local n = {1,2,3,4,5,6,7,8,9,10}
           local nLen,nIndx = #n
           for i = 1,nLen do
             nIndx=math.random(nLen+1-i)
             randList[i]=table.remove(n,nIndx)
           end
           if one and randList[#randList]==one then
             r=math.random(#randList-1)
             randList[r],randList[#randList]=randList[#randList],randList[r]
           end
           one = randList[1]
         end
         return table.remove(randList)
   end
end

test=setRandList()
for i=1,31 do
  print(test())
end
1 Like

@lzralbu and @iotaHum: thank you both very much for posting your versions to solve this problem. Something for me to study this afternoon!