This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
brumster's profile picture

Append to table with key as a string

Started by brumster, 17 January 2013 - 04:23 AM
brumster #1
Posted 17 January 2013 - 05:23 AM
Urrrgh, I'm a bit new to LUA and I need some hint on how to tackle an issue I've got.

I want to keep a table that holds details of monitored turtles (current position, status, fuel, etc) - this is to run on a console, so that a single console can be used to monitor multiple turtles over rednet. The operator can only see the details of one turtle at a time, but the program will be monitoring all rednet messages and storing the latest set of details for each turtle into a table - the key being the turtle ID, the value being another table itself with various key/value pairs in it.

The problem I have is trying to think how I can efficiently add a turtle entry into the table when a new message from a never-heard-from-before turtle hits the console.

Now, the ideal thing would be something like LUA's table.insert - but this can only insert/append elements into the table on a numerical key basis. So 1,2,3,4 etc. My turtle IDs could arrive in completely random order and will rarely be sequential. 33, 38, 15, etc.

It seems the only way I can create an entry into a table with a specified non-ordered numeric key (it could be a string, for all intents and purposes) is to create the table with the format :-


t = { [20] = tmpSubtable }

…but then I'm faced with the issue how do I add subsequent elements into the table?!

I could receive a message from a turtle, scan the existing table to see if an entry already exists (and if it does, just update it - that's easy enough). If not, I cannot obviously do just the above as it will wipe out the entirety of the existing t. So do I need to loop through it taking a copy of its elements into a temporary table, then add on my new one, then copy it all back into the original table?

My other option is to just use a table indexed numerically and sequentially, using the table.append function to add new items, and putting a key within the subtable with the turtle ID. But that then strikes me as becoming rather inefficient, as I will need to scan the entire table's entries, checking to see if the turtle has already been recorded, before doing the append. And it will be receiving messages quite regularly, so I'd like to make it as efficient as I could.

I guess the crux of the question is….
Is there a better method to insert a new k/v pair into an existing table where the keys are all non-sequential integers (or even strings)?

Apologies if this turns out to be a real example of n00bishness - I did search, honest, and I've googled, but not found anything obvious yet bar my approach above - if that's the way it needs to be done, then fair enough, but figured there might be a nicer way. Damn it, table.append is soooo close to what I need :)/> !!

edit: Or would it be best to just insert the numeric keys with nil values in the gaps? So if I have the highest current recorded turtle as 33, and a message from 38 comes in, just do 4 inserts with no values, and then the 5th insert can set the correct key entry. Obviously some minor memory overheard on the entire table but is this least of my worries if efficiency is my goal?
MysticT #2
Posted 17 January 2013 - 05:53 AM
In lua, you can add a new value to the table by simply doing:

table[key] = value
So, this should work:

local id, msg = rednet.receive()
idsTable[id] = msg
it would store the last message sent from every id.
brumster #3
Posted 17 January 2013 - 06:11 AM
I do try it that way (albeit with a bit more complexity) but it doesn't seem to add the value to the table. Let me put the code up :-


clients = {}
function monitorRednet()
while true do
  local senderId,message,distance = rednet.receive()
  -- Reminder of message format (comma-separated)
  -- LOGGERAPI,<turtle ID>,<turtle fuel range>,<turtle fuel stock in inv>,<status>,<x>,<y>,<z>,<total # items in inv>,<free slots>,[text message - may include commas]
  local tmpTable = {}
  local k = 1
  for v in string.gmatch(message, "(%w+),") do
   tmpTable[k]=v
   k=k+1
  end
  if tmpTable[1]=="LOGGERAPI" then
   local turtleId=tmpTable[2]
   print(">>"..message)
   local tmpClient = { ["time"] = os.clock(),
			 ["distance"] = distance,
			 ["range"]=tmpTable[3],
			 ["fuel"]=tmpTable[4],
			 ["status"]=tmpTable[5],
			 ["x"]=tmpTable[6],
			 ["y"]=tmpTable[7],
			 ["z"]=tmpTable[8],
			 ["inventory"]=tmpTable[9],
			 ["freeslots"]=tmpTable[10],
			 ["lastmsg"]=tostring(tmpTable[11]) -- TODO this needs to parse the end of the message line, excluding commas, since the message itself may contain some...
			}
   clients[turtleId]=tmpClient
  else
   print("Received non-loggerapi message; will ignore")
   print(">>"..message)
  end

  printAt(turtleId,12,3)
  print("Length="..#clients)
  printAt(clients[turtleId].time,12,4) ***
  printAt(clients[turtleId].distance,12,5)
  printAt(clients[turtleId].range,12,6)
  printAt(clients[turtleId].fuel,12,7)
  printAt(clients[turtleId].status,12,8)
  printAt(clients[turtleId].x..","..clients[1].y..","..clients[1].z,12,9)
  printAt(clients[turtleId].inventory,12,10)
  printAt(clients[turtleId].freeslots,12,11)
  printAt(clients[turtleId].lastmsg,2,13)

end
end


What I end up with in MC is :-


>>LOGGERAPI,34,144,47,inactive,0,0,0,0,15,INFO:Finished
Length=0
parallel:22: log.lua:115: attempt to index ? (a nil value)

For reference, *** marks line 116
The parallel:22 reference is just to the calling code - I've left some superfluous stuff out to avoid confusion.
The length=0 line verifies the table length. Obviously on line 115, where we go to print out the value, there's nothing in the referenced table hence the error.
KaoS #4
Posted 17 January 2013 - 06:19 AM

  printAt(clients[turtleId].x..","..clients[1].y..","..clients[1].z,12,9)

you are assuming here that clients[1] has been written, if not it will error because clients[1] is not a table so you are indexing it when it is nil. hence your error

edit: besides, why not just send a serialized table over rednet?

on the turtle:

rednet.open(modemSide)
local empSlots={}
local itemcount=0
for i=1,16 do
if turtle.getItemCount(i)==0 then
  table.insert(empSlots,i)
end
itemcount=itemcount+turtle.getItemcount(i)
end
local tMessage={"LOGGERAPI",["id"]=os.computerID(),["range"]=what do you mean by range?",["fuel"]=turtle.getFuelLevel(),["status"]=statusvar,["x"]=pos.x,["y"]=pos.y,["z"]=pos.z,["inventory"]=itemcount,["freeslots"]=empSlots,["lastmsg"]=msg}
rednet.send(yourID,textutils.serialize(tMessage))

On your computer

local clients={}
rednet.open(modemSide)
while true do
local id,msg,dist=rednet.receive()
tResponse=textutils.unserialize(msg)
if type(tResponse)=="table" and table.remove(tResponse,1)=="LOGGERAPI" then
  clients[table.remove(tResponse,"id")]=tResponse
end
term.clear()
term.setCursorPos(1,1)
for id,tDetails in pairs(clients) do
  for label,cont in pairs(tDetails) do
   print(label..":"..string.rep(" ",10-#label)..cont)
  end
end
end
Edited on 17 January 2013 - 05:36 AM
brumster #5
Posted 17 January 2013 - 06:36 AM
Oops, that was a hang-over from some playing about, but you're absolutely right - thanks for spotting. replace [1] with [turtleId] as per the others ;)/>

The error occurs before this, so despite just fixing it now - the problem persists.
KaoS #6
Posted 17 January 2013 - 06:42 AM
read the edit above please… a much simpler way of doing this. I avoid string.gmatch like the plague
Lyqyd #7
Posted 17 January 2013 - 06:46 AM
You declare turtleID as local to your if, so when you attempt to use it to index the table outside that if, turtleID has gone out of scope and is nil. Move the declaration out of the if and just change the value in the if.
brumster #8
Posted 17 January 2013 - 06:46 AM
Fair does - they're not in a table at the turtle end, but that's not to say they couldn't be. I shall try a re-write to your approach. Thanks Kaos, you're approach is much cleaner.
brumster #9
Posted 17 January 2013 - 06:48 AM
You declare turtleID as local to your if, so when you attempt to use it to index the table outside that if, turtleID has gone out of scope and is nil. Move the declaration out of the if and just change the value in the if.

Arggh. That's the puppy.

:unsure:/>

Need a "bang head against wall" smiley added - thanks Lyqyd. Sometimes you can't see the wood for the trees. It all works now.