Select

Posted by Metsuro on Tue 03 Jan 2006 01:01 AM — 14 posts, 63,771 views.

USA #0
Uhh i was wondering if lua had a select function like other languages, i think vbs is something like Select case ()
case "1" and case "2" or something... just wondering.
Australia Forum Administrator #1
Not built-in. Depending on what your reason is for wanting it, various variations on using tables will do the trick. There is a page of such suggestions on the Lua wiki:

http://lua-users.org/wiki/SwitchStatement


My own variant on the ideas presented is this:


function switch (selector, t)

  assert (type (t) == "table", 
          "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    print "default"
  end -- if

end -- switch


-- test


-- example table of selectors and actions

actions = {
  [1] = function () print "action 1" end ,
  [2] = function () print "action 2" end ,
  [3] = function () print "action 3" end,
  ["a"] = function () print "action a" end ,
          }


-- try to switch ...

switch (1, actions)
switch (3, actions)
switch ("a", actions)
switch (99, actions)



Here the "switch" function (you could name it "select" if you want) is used to index into a table which is keyed by the selector to give an action function. The functions shown here are inline functions, however if they were more complex they could be defined separately, like this:



function switch (selector, t)

  assert (type (t) == "table", "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    print "default"
  end -- if

end -- switch


-- test


-- example table of selectors and actions

function action_1 ()
  print "do something"
  print "action 1"
end -- action_1

function action_2_or_3 ()
  print "do something"
  print "action 2 or 3"
end -- action_2_or_3 

function action_a ()
  print "this is action 'a'"
end -- action_a 


actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  ["a"] = action_a  ,
          }


-- try to switch ...

switch (1, actions)
switch (3, actions)
switch ("a", actions)
switch (99, actions)




This also illustrates how you can "share" a function between 2 selectors (in this case, 2 and 3) by giving them both the same "action" function.


Amended on Tue 03 Jan 2006 02:52 AM by Nick Gammon
Australia Forum Administrator #2
My problem here is that the default behaviour is hard-coded into the switch function. This variation lets you supply the default action to the switch function:


function switch (selector, t, default)

  assert (type (t) == "table", "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    if type (default) == "function" then
      default ()
    end -- default supplied
  end -- if

end -- switch


-- test


-- example table of selectors and actions

function action_1 ()
  print "do something"
  print "action 1"
end -- action_1

function action_2_or_3 ()
  print "do something"
  print "action 2 or 3"
end -- action_2_or_3 

function action_a ()
  print "this is action 'a'"
end -- action_a 

actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  ["a"] = action_a  ,
          }

function default ()
  print "default taken here"
end -- default

-- try to switch ...

switch (1, actions, default)
switch (3, actions, default)
switch ("a", actions, default)
switch (99, actions, default)



Alternatively, you might simply want to have a table entry called "default" if you didn't think that would be needed as a selector, and use that if the initial lookup didn't match.
Amended on Tue 03 Jan 2006 02:52 AM by Nick Gammon
Australia Forum Administrator #3
Here is an example of having the default action in the table, which seems a bit neater to me. In this case I have chosen a key I think no-one would want in practice, which is 1/0 (divide by zero).

This seems to work OK, as typing:

/print (1/0) --> 1.#INF

Thus, you get a key that is unlikely to be needed in general use.


function switch (selector, t)

  assert (type (t) == "table", "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    f = t [1/0]
    if type (f) == "function" then
      f ()
    end -- default in table
  end -- if

end -- switch


-- test


-- example table of selectors and actions

function action_1 ()
  print "do something"
  print "action 1"
end -- action_1

function action_2_or_3 ()
  print "do something"
  print "action 2 or 3"
end -- action_2_or_3 

function action_a ()
  print "this is action 'a'"
end -- action_a 

function action_default ()
  print "default action"
end -- action_a 

actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  ["a"] = action_a  ,

  [1/0] = action_default
          }

-- try to switch ...

switch (1, actions)
switch (3, actions)
switch ("a", actions)
switch (99, actions)
Amended on Tue 03 Jan 2006 02:53 AM by Nick Gammon
Australia Forum Administrator #4
And to make that even neater, define a variable "DEFAULT" so we don't have to remember what our default value is, and change it if, for some reason, 1/0 doesn't work for us:


DEFAULT = 1/0  --> special key for default case

function switch (selector, t)

  assert (type (t) == "table", "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    f = t [DEFAULT]
    if type (f) == "function" then
      f ()
    end -- default in table
  end -- if

end -- switch


-- test


-- example table of selectors and actions

function action_1 ()
  print "do something"
  print "action 1"
end -- action_1

function action_2_or_3 ()
  print "do something"
  print "action 2 or 3"
end -- action_2_or_3 

function action_a ()
  print "this is action 'a'"
end -- action_a 

function action_default ()
  print "default action"
end -- action_a 

actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  ["a"] = action_a  ,

  DEFAULT = action_default
          }

-- try to switch ...

switch (1, actions)
switch (3, actions)
switch ("a", actions)
switch (99, actions)
Amended on Tue 03 Jan 2006 02:52 AM by Nick Gammon
Australia Forum Administrator #5
A final version, once we have defined "switch" we can inline the table definition, as an anonymous inline table, to save having to define it separately:


function switch (selector, t, default)

  assert (type (t) == "table", "Table not supplied to switch")

  local f = t [selector]

  if f then
    assert (type (f) == "function", "Not a function")
    f ()
  else
    if type (default) == "function" then
      default ()
    end -- default supplied
  end -- if

end -- switch


-- test


function myfunc (sel)

  switch (sel,
    {
    [1] = function () print "action 1" end,
    [2] = function () print "action 2" end,
    [42] = function () print "action 42" end,
    },

    -- default:

    function () print "default taken" end
         ) -- end of switch

end -- myfunc

myfunc (1)
myfunc (2)
myfunc (42)
myfunc ("blah")

Amended on Tue 03 Jan 2006 03:02 AM by Nick Gammon
USA #6
Apparently others have found similar solutions:

http://lua-users.org/wiki/SwitchStatement

The last one on the page is supposed to handle more complex situations, such as fall throughs and ranges of values. But I have to say, nasty surprises like this, where an otherwise good language (scripting or otherwise) is missing *basic* functions, drives me batty. Especially if they are ones I know I use fairly often. And this is one of those cases. :p
Australia Forum Administrator #7
I referenced that Wiki item in my first reply. :)

Quote:

... an otherwise good language (scripting or otherwise) is missing *basic* functions ...


Well, C doesn't fully support it either, and I hope you aren't going to say C drives you batty. Take an example from the SMAUG source:


SPELL_FUN *spell_function( char *name )
{
   if( !str_cmp( name, "spell_smaug" ) )
      return spell_smaug;
   if( !str_cmp( name, "spell_acid_blast" ) )
      return spell_acid_blast;
   if( !str_cmp( name, "spell_animate_dead" ) )
      return spell_animate_dead;
   if( !str_cmp( name, "spell_astral_walk" ) )
      return spell_astral_walk;
... and so on ...


What is this doing? It is converting a name to a function, and it is doing it like this because the C switch statement doesn't handle strings, only numeric constants. There are 189 lines in this function (and it isn't the only one with this general design). Imagine how slow it is if the spell name you want is at the end of the list - or, not there at all.

In Lua you could have a simple table:


spellnames = {
  spell_smaug = spell_smaug,
  spell_acid_blast = spell_acid_blast,
  spell_animate_dead = spell_animate_dead,
  spell_astral_walk = spell_astral_walk,
-- and so on 
  }


This is Lua shorthand for keying a string literal to a value, in this case we assume a function. It is the same as:


spellnames = {
  ["spell_smaug"] = spell_smaug,
  ["spell_acid_blast"] = spell_acid_blast,
  ["spell_animate_dead"] = spell_animate_dead,
  ["spell_astral_walk"] = spell_astral_walk,
-- and so on 
  }


Now in Lua, you can do a direct table lookup, which is faster and neater. Plus, you can simply generate the "reverse" table in a couple of lines of code:


rev_spellnames = {}
for k, v in pairs (spellnames) do
  rev_spellnames [v] = k
end


This generates, from the first table, a table which keys the function addresses to their names, so you can convert them back the other way.

You can't do that with a switch statement, which means that the C code (eg. SMAUG) tends to be a lot more cluttered, and stuff like adding new functions means you have to add them to a switch statement, *and* add them to the "reverse" function (a lengthy if statement) as well.

Effectively the table-driven approach forces you to think about what you are really trying to achieve and make a cleaner design.
Amended on Tue 03 Jan 2006 05:07 AM by Nick Gammon
USA #8
Oops. Didn't notice the reference. lol

But yeah, C some similar issues. But there are probably more efficient ways to impliment it is C than a lot of if then statements too, it would just be so esoteric that no one would understand it. lol Frankly, I haven't used C much, so didn't know about that limitation, but yeah, that would annoy me a tad to discover. Though, in C you would most likely use an enumerated list and then 'still' reference a number.
#9
@Nick Gammon
The normal way to index a table with a key that no one else would use in lua is this:

default = {}
table = {default = "private value"}

By using that, you can nearly guarantee that it won't conflict.

=)
USA #10
You'd actually need:

t = { [default] = "private value"}

otherwise you're just setting the string key "default".

Also, you'd probably make default a local variable to avoid polluting the global namespace, and to make sure that only the current chunk of code can see it.
Australia Forum Administrator #11
In that case it looks like my earlier post was wrong:


actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  [\"a"] = action_a  ,

  DEFAULT = action_default
          }


It should have read:


actions = {
  [1] = action_1  ,
  [2] = action_2_or_3  ,
  [3] = action_2_or_3 ,
  ["a"] = action_a  ,

  [DEFAULT] = action_default
          }
Australia Forum Administrator #12
However, Mania is right, using a table as a key effectively gives you an address that should be unique. A possible problem with that is serializing the table, but that may have applied to the 1/0 case anyway.
USA #13
You could also set your Default action to the __index metatable, then you don't have to worry about another key stepping on it in the table.