Get Random Value from Table

bane_

Howardianism High-Priest
Joined
Nov 27, 2013
Messages
1,559
I use this mini-function to get random values from tables:
Code:
function tablerandom(tbl)
	local keys = {}
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	local randIndex = math.random(#keys) -- this is line 23
	local randKey = keys[randIndex]
	return tbl[randKey]
end

It's been working quite well for me, but now that I'm doing some AI-tuning (holy crap, how this **** is hard) I'm getting this error:
[32015.632] Runtime Error: C:\Users\bane_\Documents\My Games\Sid Meier's Civilization 5\MODS\Crane Clan (v 1)\Lua\Lib\DomotaiUtils.lua:23: bad argument #1 to 'random' (interval is empty)

It happens when it gets a nil interval (as explicitly told by the log :lol:), BUT I wanna know why this is the first time I'm getting those.
I was using table.insert to put in IDs from workers, like this:

Spoiler :
Code:
for pUnit in pPlayer:Units() do
	if pUnit:GetUnitType() == GameInfoTypes["UNIT_KAKITA_ARTISAN"] and pUnit:GetPlot():GetOwner() == iPlayer then
		table.insert(CraneAvWorkers, pUnit:GetID())
		bCraneAvWorkers = true
		print("A Crane worker was inserted into the 'CraneAvWorkers' table")
	elseif pUnit:GetUnitType() == GameInfoTypes["UNIT_KAKITA_ARTISAN"] and not pUnit:IsBusy() and pUnit:GetPlot():GetOwner() ~= iPlayer and pUnit:GetPlot():GetOwner() >= 0 then
		local pPlot = pUnit:GetPlot()
		local iUnit = pUnit:GetID()
		LookForSuitableGardens(iUnit, pPlot, false)
		print("Looking for suitable place for a garden with boolean as 'false'.")
	end
end

So I suspect the problem is that the ID from the Worker will not be contiguously, leaving small breaches between one number and the other. I imagine using CraneAvWorkers[iUnitID] (with iUnitID defined, obviously) will result in the same eventual errors.

Any ideas how to fix this?
 
The problem is almost certainly being caused by calling the tablerandom function with an empty tbl parameter, ie tablerandom({}), which will possibly happen when the player has no workers
 
Code:
function tablerandom(tbl)

	if tbl == nil then
		print("OHNOES! a nil value!")
		return 9000
	end

	local keys = {}
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	local randIndex = math.random(#keys) -- this is line 23
	local randKey = keys[randIndex]
	return tbl[randKey]
end
 
If tbl was nil, you'd get a "bad argument #1 to 'pairs' (table expected, got nil)" error, the problem is being caused by tbl being empty, ie {}, and doing math.random(0), which will cause the "bad argument #1 to 'random' (interval is empty)" error
 
The code in the spoiler does not call the tablerandom() function, so without the full mod, it is impossible to say under what circumstances you are effectively calling tablerandom({})
 
DarkScythe put me onto this a while back, which is often useful to me for trouble-shooting little bits of lua behavior. lua demo page

You could dummy up some conditions and see which ones are going to cause a similar error, so you'll at least know which possible conditions you need to take action within your lua to avoid.

doing this:
Code:
function tablerandom(tbl)

	if tbl == nil then
		print("OHNOES! a nil value!")
		return 9000
	end

	local keys = {}
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	local randIndex = math.random(#keys) -- this is line 23
	local randKey = keys[randIndex]
	return tbl[randKey]
end
joe = {}
tablerandom(joe)
in that demo program gives me this error:
Code:
input:12: bad argument #1 to 'random' (interval is empty)

[edit]I also was under the impression that the keys (k) had to be numerical and sequential in order for #keys to return anything except a nil value.
 
[edit]I also was under the impression that the keys (k) had to be numerical and sequential in order for #keys to return anything except a nil value.

keys will be numerical and sequential, because it has been constructed from table.insert() calls

if tbl is {"a"="xyz", "f"="lmn", "z"=12}
then keys[1]="a", keys[2]="f" and keys[3]="z", so #keys will be 3

exactly what is required

however if tbl = {}
then keys will be empty, so #keys will be 0, so math.random(0) will throw the error being reported
 
Code:
table.insert(keys, k)
not sure how I overlooked that part. :hammer2:

Nice workaround for the table length issue, BTW, bane_. I may have to steal borrow this at some future point. :)
 
@Bobert13: The print won't help much, unfortunately, since I already have a confirmation when things go wrong. I need to find a way for them to NOT go wrong. Also, why return 9000? Didn't get that part.

so without the full mod
You are right. I should've posted it all at first. Here it is:

-AIHelper.lua
Spoiler :
Code:
for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
	print("Checking now if anyone is friend with the Crane. Only going forward if that's true and CraneAvWorkers isn't nil.")
	if Players[i]:IsDoF(iPlayer) and bCraneAvWorkers then
		for pCity in Players[i]:Cities() do
[B]			local iCraneWorker = tablerandom(CraneAvWorkers)[/B]
			local bCraneWorkerUsed = IsWorkerUsed(iCraneWorker)
			print("Checking if the Worker is already used. Is it?" ..tostring(bCraneWorkerUsed))
			[B]while bCraneWorkerUsed do
				print("It was true. Trying again...")
				iCraneWorker = tablerandom(CraneAvWorkers)
				bCraneWorkerUsed = IsWorkerUsed(iCraneWorker)
				print("Second Check: Is the Worker already used?" ..tostring(bCraneWorkerUsed))
			end[/B]
			table.insert(CraneAvWorkers, pUnit:GetID())
			local pUnit = pPlayer:GetUnitByID(iCraneWorker)
			local pPlot = Map.GetPlot(pCity:GetX(), pCity:GetY())
			for idX = -2, 2, 1 do
				for idY = -2, 2, 1 do
					local pTargetPlot = Map.PlotXYWithRangeCheck(pPlot:GetX(), pPlot:GetY(), idX, idY, 2)
					if pTargetPlot and pTargetPlot:GetOwner() == i and pTargetPlot:CanHaveImprovement(GameInfoTypes["IMPROVEMENT_FANTASTIC_GARDENS"], NO_TEAM) then
						pUnit:PushMission(MissionTypes.MISSION_MOVE_TO, pTargetPlot:GetX(), pTargetPlot:GetY(), 0, 0, 1)
						LookForSuitableGardens(iUnit, pPlot, true)
						print("Looking for suitable place for a garden with boolean as 'true'.")
						bBreakReal = true
						break
					end
					if bBreakReal then
						break
					end
				end
				if bBreakReal then
					bBreakReal = false
					break
				end
			end
		end
	end
end

-DomotaiUtils.lua
Spoiler :
Code:
iCraneClan = 0

function GetCranePlayer()
	for i = 0, GameDefines.MAX_MAJOR_CIVS - 1, 1 do
		Players[i]:SetNumFreePolicies(1)
		Players[i]:SetNumFreePolicies(0)
		if Players[i]:GetCivilizationType() == GameInfoTypes["CIVILIZATION_CRANE_CLAN"] then
			iCraneClan = i
			print("Crane Clan ID: " ..iCraneClan)
		end
	end
end

function GetRandom(iMin, iMax)
	return Map.Rand((iMax + 1) - iMin, "") + iMin
end

[B]function tablerandom(tbl)
	local keys = {}
	for k in pairs(tbl) do
		table.insert(keys, k)
	end
	local randIndex = math.random(#keys)
	local randKey = keys[randIndex]
	return tbl[randKey]
end[/B]

[...]

Nice workaround for the table length issue, BTW, bane_. I may have to steal borrow this at some future point. :)

Are you kidding? It's property of every one. It's already yours as well. :)
 
Top Bottom