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

How to make and use a look-up table

Started by Sora Firestorm, 17 February 2013 - 07:28 PM
Sora Firestorm #1
Posted 17 February 2013 - 08:28 PM
Hello, I'm going to talk about look-up tables. Look-up tables are a very useful, space- and time-saving construct that
every programmer should know how to use.

First off, let's talk about their use. A look-up table's primary use is to replace the ugly and time consuming method of if-elseif chains, like this one


if a then
  -- stuff
elseif b then
  -- other stuff
elseif c then
  -- more stuff
elseif d then
  -- even more stuff
elseif e then
  -- too much stuff
else
  -- Something we didn't expect
end

That looks just plain ugly. It also looks hard to write, which it is. So we're going to fix it.

Consider these two functions :

function myFunction()
  print("Hello!");
end

function myOtherFunction()
  print("Goodbye!");
end

I'm collecting input from the user, and I want to run myFunction when the user enters "hi" and run myOtherFunction when the user enters "bye."

In order to do this, we create a table. In this table, the key corresponds to the function I want to run if I receive it.
Let's make a table to do this :

local myLookupTable = {
  ["hi"] = myFunction,
  ["bye"] = myOtherFunction,
};

Notice how when I typed the function names, I did not use parenthesis with them. This is because I am not calling them, but getting the code they point to.

Now, all I have to do to check the user input is this

if myLookupTable[input] then
  myLookupTable[input]();
else
   -- Just in case the user types a word that we don't know about.
   print("Word not found");
end

If you look closely, I put parenthesis after the second myLookupTable[input]. Because the data at myLookupTable[input] is a run-able function, all I have to do to run it is to call it by adding parenthesis.

Now my word asking code looks like this

write("Type a word : ");
local input = read();

if myLookupTable[input] then
  myLookupTable[input]();
else
  print("Word not found");
end

And as TheOriginalBIT has pointed out, you don't necessarily need to have your lookup table made of functions.
Consider BIT's code :

local validSides = {
  ["left"] = true,
  ["right"] = true,
  ["up"] = true,
  ["down"] = true,
}

write("Type a side: ")
local input = read()

if validSides[input] then
  print("Thank you")
else
  print(input.." is not a valid side")
end

I can see how you might think that the lookup table didn't really make our code any less ugly,
and that typing if-elseif-else-end wouldn't be too hard. I'd agree with that statement.
Remember though, that there are times when you can be working with 4+ different expected outputs,
at which point the if-elseif-else-end isn't going to cut it.
Using a lookup table for smaller checks might not be necessary, but for bigger checks it is vital if you want clean, readable code.

That is all. Be sure to make friends with your look-up tables. They will certainly make life easier in bigger programs.

Please post any comments, suggestions, or questions.

~Sora
theoriginalbit #2
Posted 17 February 2013 - 09:02 PM
Nice tutorial. :)/>

Just a point of improvement:
SpoilerThis…


if myLookupTable[input] ~= nil then
  myLookupTable[input]();
else
  print("Word not found")
end

Is the same as saying

if myLookupTable[input] then
  myLookupTable[input]()
else

  print("Word not found")
end
As in Lua nil and false are resolved the same, and if something is in the table, then clearly its not nil, so its evaluated to true

And an addition to what you have said
SpoilerThere is another adaption that you can do on your example for if you don't want to call a function

local validSides = {
  ["left"] = true,
  ["right"] = true,
  ["up"] = true,
  ["down"] = true,
}

write("Type a side: ")
local input = read()

if validSides[input] then
  print("Thank you")
else
  print(input.." is not a valid side")
end
This is actually what the CC devs use in the 'edit' program to do some things, it doesn't call a function
Sora Firestorm #3
Posted 17 February 2013 - 09:17 PM
Thank you, BIT.

I've fixed the code and added your example.

~Sora
theoriginalbit #4
Posted 17 February 2013 - 09:37 PM
Thank you, BIT.
No problems. And thank you, so many people say OriginalBIT, I don't get it, why take of 3 letters (The), type the whole damn thing or BIT, so thank you :)/>
Pavlus #5
Posted 20 February 2013 - 09:34 AM
Good tut. I use this technique a lot. This is my implementation of inverting a given direction for turtle:

-- direction constants. Format is:
-- shortcut = "<Peripheral side>" --direction
local du="top" --up
local dd="bottom" --down
local df="front" --forward
local db="back" --back
local dl="left" --left
local dr="right" --right

function revDir(dir)
--reverses given direction. Takes and returns a directional constant
	local tt={[df]=db,[db]=df,[du]=dd,[dd]=du,[dl]=dr,[dr]=dl}
	return tt[dir], not tt[dir] and 'Unknown direction:'..tostring(dir)
end

--usage:
local newdir,error = revDir(dir)
if error then print(error) end
theoriginalbit #6
Posted 20 February 2013 - 09:51 AM
Good tut. I use this technique a lot. This is my implementation of inverting a given direction for turtle:

-- direction constants. Format is:
-- shortcut = "<Peripheral side>" --direction
local du="top" --up
local dd="bottom" --down
local df="front" --forward
local db="back" --back
local dl="left" --left
local dr="right" --right

function revDir(dir)
--reverses given direction. Takes and returns a directional constant
	local tt={[df]=db,[db]=df,[du]=dd,[dd]=du,[dl]=dr,[dr]=dl}
	return tt[dir], not tt[dir] and 'Unknown direction:'..tostring(dir)
end

--usage:
local newdir,error = revDir(dir)
if error then print(error) end
Functionally that is no different to my method. its just a little slower as your constructing the table each time as opposed to just making it once and using it.
HeroOnSocks #7
Posted 20 February 2013 - 07:00 PM
Thanks for this tut, it helped in something I was trying to do. :)/>

and this may be a bit over my head, but why does encapsulating the name of the function in double quotes not work? The name stored within the table, that is. I'm keen to think that it would need to see it's a string to apply the name before the (). I did a bit of Googling, but I'm still uncertain (but more importantly am just happy it does work without!).
Alerith #8
Posted 21 February 2013 - 08:42 AM
Thanks! Really helped :D/>
Sora Firestorm #9
Posted 21 February 2013 - 02:35 PM
Thanks for this tut, it helped in something I was trying to do. :)/>

and this may be a bit over my head, but why does encapsulating the name of the function in double quotes not work? The name stored within the table, that is. I'm keen to think that it would need to see it's a string to apply the name before the (). I did a bit of Googling, but I'm still uncertain (but more importantly am just happy it does work without!).

Could you quote the piece of code you have a question about, please? I want to make sure we both have the same question before I answer it for you. :)/>

And to everyone else : Thanks for taking a look! I'm glad I was able to teach people something! :D/>
R167 #10
Posted 21 February 2013 - 03:19 PM
Thank you. Knew the basic concepts of this and how the programs like "go" worked, but this is very helpful. Thanks. :)/>
remiX #11
Posted 22 February 2013 - 08:57 AM
Nice tut :)/>
This should help a lot of beginners and reduce the amount of posts in Ask a Pro

No problems. And thank you, so many people say OriginalBIT, I don't get it, why take of 3 letters (The), type the whole damn thing or BIT, so thank you :)/>

What about TheBit? :P/>
LBPHacker #12
Posted 22 February 2013 - 09:25 AM
This should help a lot of beginners and reduce the amount of posts in Ask a Pro

We have no such luck… Most of the questions are still about "attempt to index nil"s or "too long without yielding"s…
electrodude512 #13
Posted 22 February 2013 - 10:26 AM
Thanks for this tut, it helped in something I was trying to do. :)/>

and this may be a bit over my head, but why does encapsulating the name of the function in double quotes not work? The name stored within the table, that is. I'm keen to think that it would need to see it's a string to apply the name before the (). I did a bit of Googling, but I'm still uncertain (but more importantly am just happy it does work without!).

If you have:
local myLookupTable = {
  ["hi"] = myFunction,
  ["bye"] = myOtherFunction,
};
then myLookupTable["hi"] returns a function, myFunction. Calling myLookupTable["hi"]() is the same as calling myFunction().

However, if you have:
local myLookupTable = {
  ["hi"] = "myFunction",
  ["bye"] = "myOtherFunction",
};
then myLookupTable["hi"] returns a string containing the text "myFunction". Calling myLookupTable["hi"]() in this case is like trying to call "myFunction"(), which doesn't make any sense, because you can't call a string.

I hope I answered your quesition.

electrodude
Pharap #14
Posted 06 March 2013 - 03:46 PM
Useful/Useless trivia -
This is possible because lua implements hash-tables behind the scenes to allow for speedy item lookup when given a string to hash.
Lua does/should also only instantiate a hash-table when someone uses a string as a key, so unless the table is needed, it isn't made.
I think Lua deserves a hearty cheer for being so clever.
Engineer #15
Posted 11 March 2013 - 10:46 AM
I already used this quite a lot, but it is nice for newer users of LUA :)/>

Nice tutorial
ThatNewb #16
Posted 12 March 2013 - 09:23 AM
very useful and full of info
xgaarocha #17
Posted 12 March 2013 - 02:32 PM
I was going to say: Well, use switches then!
Then I remembered that LUA hates to be mainstream and do things other languages already do :/

Oh well…
theoriginalbit #18
Posted 12 March 2013 - 02:35 PM
I was going to say: Well, use switches then!
Then I remembered that LUA hates to be mainstream and do things other languages already do :/

Oh well…
Even languages that have switches there are still advantages to using lookup tables. Think of dynamic content. You can use a look-up table, but you cannot use a switch.
Simon #19
Posted 12 March 2013 - 02:52 PM
Hmm, I have made similar things before, feel free to use this.

color = {
  [1] = 1	-- White
  [2] = 2	-- Orange
  [3] = 4	-- Magenta
  [4] = 8	-- Light Blue
  [5] = 16	-- Yellow
  [6] = 32	-- Lime
  [7] = 64	  -- Pink
  [8] = 128	-- GGray
  [9] = 256	-- Light Gray
  [10] = 512	-- Cyan
  [11] = 1024	-- Purple
  [12] = 2048	-- Blue
  [13] = 4096	-- Brown
  [14] = 8192	-- Green
  [15] = 16384	-- Red
  [16] = 32768
}
useful example:

colors[math.random(16)]
or
print("Pick a color, 1 through 16")
colors[read()]
theoriginalbit #20
Posted 12 March 2013 - 02:57 PM
Hmm, I have made similar things before, feel free to use this.

colors = {
  [1] = 1
  [2] = 2
  [3] = 4
  [4] = 8
  [5] = 16
  [6] = 32
  [7] = 64
  [8] = 128
  [9] = 256
  [10] = 512
  [11] = 1024
  [12] = 2048
  [13] = 4096
  [14] = 8192
  [15] = 16384
  [16] = 32768
}
useful example:

colors[math.random(16)]
or
print("Pick a color, 1 through 16")
colors[read()]
Firstly, you are overriding the colors table, meaning you would no longer be able to use colors.white, colors.red etc etc etc. So thats a big no, no.

Secondly, with how the colours work (they area binary scale) that code can actually be simplified to this

local col = 2 ^ math.random(0,15)
or

print("Pick a color, 0 through 15")
local col = 2 ^ tonumber( read() )
xgaarocha #21
Posted 13 March 2013 - 01:19 PM
Even languages that have switches there are still advantages to using lookup tables. Think of dynamic content. You can use a look-up table, but you cannot use a switch.

Yeah, but for simple things, like checking input, switches are better in my opinion :)/>
AgentE382 #22
Posted 09 August 2013 - 07:19 PM
I'm going to be a pain here, but I saw this in the "Ask a Pro Renewal Project" and just had to find this thread to fix it.
<snip>


function myFunction()
  print("Hello!");
end

function myOtherFunction()
  print("Goodbye!");
end

<snip>


local myLookupTable = {
  ["hi"] = myFunction,
  ["bye"] = myOtherFunction,
};

<snip>


if myLookupTable[input] then
  myLookupTable[input]();
else
   -- Just in case the user types a word that we don't know about.
   print("Word not found");
end

<snip>


write("Type a word : ");
local input = read();

if myLookupTable[input] then
  myLookupTable[input]();
else
  print("Word not found");
end

<snip>

[rant]
You don't need any of those semicolons. Lua is being nice to you and tolerating them. (In Lua 5.1, they can optionally follow a statement. They cannot define empty statements by themselves, like in C.) But using them after each statement in Lua is completely redundant, unlike using them in C.

Their only useful purpose is as a table field separator. In other words, use them exactly like, or instead of ",".

Also, their behavior changes between Lua 5.1 and Lua 5.2, just to give you a heads-up. I don't use them because I normally write for 5.2, but CC uses 5.1, so I need to keep my stuff compatible with both (and I don't like typing them).
[/rant]

—— Serious part ——

Using them that way is legal and you can use one after every statement if you want, but please refrain from doing so in beginner tutorials. That was very confusing for me as a beginner, having previous programming experience.
Brekkjern #23
Posted 09 August 2013 - 09:02 PM
How would you pass a function with arguments from such a table? Would that even work?
AgentE382 #24
Posted 09 August 2013 - 10:39 PM
How would you pass a function with arguments from such a table? Would that even work?

It depends. What exactly are you asking?

If you're asking "Can you pass arguments to functions stored in tables like that?", then the answer is "Yes."

This code:

function hello(name)	-- This function takes a single argument
	print("Hello, " .. name .. "!")	-- and uses the argument in its body.
end
function goodbye(name)	-- Same here.
	print("Goodbye, " .. name .. ".")
end
local lookupTable = {
	[1] = hello,
	[2] = goodbye
}
is equivalent to this code:

local lookupTable = {
	[1] = function(name)	-- Exactly the same as above, except we are directly storing the function in a table
		print("Hello, " .. name .. "!")	-- instead of storing it in a variable then putting the variable in the table.
	end,
	[2] = function(name)
		print("Goodbye, " .. name .. ".")
	end
}
You would use either like this:

lookupTable[1]("Your Name")	-- Call functions stored in tables exactly the same as a function normally stored in a variable.
lookupTable[2]("Your Name")

You could also do this:

local hello = lookupTable[1]	-- Take the function stored in the table and put it into a variable.
local goodbye = lookupTable[2]

hello("Your Name")	-- Then call the variable like normal.
goodbye("Your Name")

Functions are first-class values in Lua. In other words, whatever you can do to another type of value, you can (theoretically) do to a function. (I won't get into how to make that happen. It won't work unless you set it up yourself.)

So, you can store functions in a table and call them with or without any arguments, just like you would store them in a variable (the function name is actually a variable) and call them normally.

I hope that makes some sense. I'm kinda' tired right now. ;-)
Brekkjern #25
Posted 09 August 2013 - 10:49 PM
On further thought, I figured out how myself. I tried doing too much with just one table.
Kingdaro #26
Posted 10 August 2013 - 03:09 AM
Not sure if anyone pointed this out, but this table:


local validSides = {
  ["left"] = true,
  ["right"] = true,
  ["up"] = true,
  ["down"] = true,
}

Can be rewritten as:

local validSides = {
  left = true,
  right = true,
  up = true,
  down = true,
}

And the script would still work, as in, validSides["left"] and validSides.left would be the same thing.