views:

1095

answers:

11

A dispatch table is a data structure that associates an index value to an action. It's a rather elegant replacement for a switch-type statement. Most languages have support for dispatch tables, but the support ranges from do-it-yourself to built-in and hidden under a layer of syntax. How does your favorite language implement dispatch tables?


I had considered using the example from the Wikipedia page, but it's a bit contrived without being clean. Instead, I recommend implementing a dispatch table to play Rock Paper Scissors. First create a data structure that stores actions that print a rock, paper or scissors. Then demonstrate how to use the structure by making a throw or two. I will post an answer in Lua for the sake of example.


Bonus: One of great things about a dispatch table is that actions may be added dynamically. There are numerous expansions of the basic RPS game, including dynamite, which I remember from childhood. (Dynamite blows up rock and scissors cut wick. In our games dynamite blew up paper too.) How do you extend a dispatch table in your language?

+2  A: 

Lua

The dispatch table could look like:

local throw = {
   rock = function () io.write("O\n") end,
   paper = function () io.write("~\n") end,
   scissors = function () io.write(">8\n") end,
}

This works well for Bart's strategy:

-- Bart's brain: Good ol' `rock'.  Nuthin' beats that!
-- http://www.snpp.com/episodes/9F16.html
throw.rock()

But Lua also supports numeric indexing which works better for a random strategy:

throw[1] = throw.rock
throw[2] = throw.paper
throw[3] = throw.scissors

-- Seed the random number generator
math.randomseed(os.time())

throw[math.random(3)]()

Adding a throw is easy:

throw.dynamite = function () io.write("[]~\n") end

throw.dynamite()


Addressing Brad Gilbert's comment, there's no reason a dispatch table couldn't be multidimensional. For instance, here's one implementation of a dispatch table that plays Rock Paper Scissors:

local player = {0, 0}

local play = {
   rock = {
      rock = function () io.write("push\n") end,
      paper = function () 
                 io.write("Rock covered by paper!\n"); 
                 player[2] = player[2]+1
              end,
      scissors = function () io.write("Rock smashes scissors!\n"); 
                 player[1] = player[1]+1
               end,
   },
   paper = {
      rock = function () 
                io.write("Paper covers rock!\n"); 
                player[1] = player[1]+1 
             end,
      paper = function () io.write("push\n") end,
      scissors = function () io.write("Paper cut by scissors!\n"); 
                 player[2] = player[2]+1
               end,
   },
   scissors = {
      rock = function () 
                io.write("Scissors smashed by rock!\n"); 
                 player[2] = player[2]+1
              end,
      paper = function () 
                 io.write("Scissors cut paper!\n"); 
                 player[1] = player[1]+1
              end,
      scissors = function () io.write("push\n") end,
   },
}

A function could then be called thusly:

results.rock.paper()

But more usefully:

results["scissors"]["paper"]()

In this particular case, the pattern is regular enough that a data lookup might work better than a function lookup. If I were to expand the game to include other throws, I'd definitely want to create a function that generates the dispatch table rather than construct it by hand. The point is nothing prevents a dispatch table from being multidimensional.

Jon Ericson
Lua is interesting; the "table" seems to find a middle ground between similar structures in R and Python. R's list indexing (double braces) is awkward. Numeric indexing doesn't make as much sense with Python dictionaries, though it will get ordered dictionaries soon. (Although, for a random throw, order isn't really relevant.)
ars
+2  A: 

C

#define MAX_THROW_KINDS 5

static void rock() { puts("O"); }
static void paper() { puts("~"); }
static void scissors() { puts(">8\n"); }
struct {
    char *name;
    void (*action)();
} throw[MAX_THROW_KINDS] = {
    {"rock", &rock},
    {"paper", &paper},
    {"scissors", &scissors},
    /* the remainder is initialized to NULL */
};

/* throw rock */
(*throw[0].action)();

/* random action */
int num_throws;
for (num_throws = 0; num_throws < MAX_THROW_KINDS; num_throws++)
    if (!throw[num_throws].action) break;
srand(time());
(*throw[rand() % num_throws].action)();

/* add an action */
static void dynamite() { puts("[]"); }
if (num_throws < MAX_THROW_KINDS) {
    throw[num_throws].name = "dynamite";
    throw[num_throws].action = &dynamite;
    num_throws++;
}
ephemient
+1  A: 

Haskell

{-# LANGUAGE ViewPatterns #-}

import System.Random

type Throws = [(String, IO ())]

throwRock :: Throws -> IO ()
throwRock (lookup "rock" -> Just rock) = rock

throwRandom :: Throws -> IO ()
throwRandom ts = do
    i <- randomIO
    snd $ ts !! (i `mod` length ts)

main = do
    let throws = [ ("rock", putStrLn "O")
                 , ("paper", putStrLn "~")
                 , ("scissors", putStrLn ">8")
                 ]
    throwRock throws
    sequence_ . replicate 3 $ throwRandom throws
    sequence_ . replicate 5 $
        throwRandom $ throws ++ [("dynamite", putStrLn "[]")]

Okay, in reality you'd probably rather

throwRock ts = case lookup "rock" ts of
    Just rock -> rock; _ -> error "no \"rock\" action found"

instead of depending on GHC 6.10's -XViewPatterns, but it looks nicer the unportable way.

ephemient
+2  A: 

Perl 5

my %dispatch = (
  monday    => \&weekday,
  tuesday   => \&weekday,
  wednesday => \&weekday,
  thursday  => \&weekday,
  friday    => \&weekday,
  saturday  => \&weekend,
  sunday    => \&weekend,
);

sub weekday{ ... }
sub weekend{ ... }

$dispatch{ lc day_of_week() }->();
Brad Gilbert
+4  A: 

C#:

I wrote this from scratch in the browser, so I might have mucked something up. You can change this to fit usage by changing Action to Action or Func type and modifying the Dispatch method.

public class Main
{
    public static int Main()
    {
        var dispatcher = new DispatchTable<string>();
        dispatcher.AddAction("paper", Paper);
        dispatcher.AddAction("rock", Rock);
        dispatcher.AddAction("scissors", Scissors);

        dispatcher.Dispatch("paper")
    }

    public void Paper() { }
    public void Scissors() { }
    public void Rock() { }

}

public class DispatchTable<T>
{
    private readonly Dictionary<T, Action> table = new Dictionary<T, Action>();

    public void AddAction(T key, Action action) { table[key] = action; }

    public void Dispatch(T key)
    {
        Action action;
        if(table.TryGetValue(key, out action))
        {
            action();
        }
        else throw new InvalidOperationException("Action not supported: " + key.ToString());
    }
}
Jamie Penney
As I'm (mostly) C# illiterate, would you mind adding an example how to use this? I'm guess you a) instantiate a DispatchTable variable, b) call its AddAction method for each action you want to implement, and c) call its Dispatch method when you want to lookup an action. But I'm in the dark about what the syntax would be for these steps.
Jon Ericson
Thanks! I think I understand it now.
Jon Ericson
A: 

R

throw = list(rock=function() print('O'),
             paper=function() print('~'),
             scissors=function() print('>8'))


# bart's strategy
throw$rock()

# index by character vector
throw[['rock']]()

# numerical indexing
throw[[1]]()

# random throw
throw[[sample(1:3, 1)]]()

# add a throw
throw$dynamite <- function() print('[]~')
throw$dynamite()
ars
A: 

Python

import sys, random
w = sys.stdout.write

throw = dict(rock=lambda: w('O\n'),
             paper=lambda: w('~\n'),
             scissors=lambda: w('>8\n'))

# bart's strategy
throw['rock']()

# random throw
#   throw.values() returns list of values in dict
#   which can be indexed numerically
throw.values()[random.randint(1, 3)]()

throw['dynamite'] = lambda: w('[]~\n')
throw['dynamite']()
ars
A: 

C++

#include <unordered_map>
#include <string>
#include <vector>
#include <functional>
#include <iostream>
#include <cstdlib>

using namespace std;


void print(char const *what)
{
 cout<<what<<endl;
}

int main()
{
 unordered_map<string,function<void()> > thrower;
 thrower["rock"]=bind(&print,"O");
 thrower["paper"]=bind(&print,"~");
 thrower["sissors"]=bind(&print,">");

 char const  *keys[3]={"rock","paper","sissors"};

 thrower[keys[rand() % 3]]();

 thrower["dynamite"]=bind(&print,"[]~>");

}
Artyom
+2  A: 

Ruby

  throw = {
    :rock => lambda { puts "0" },
    :paper => lambda { puts "~" },
    :scissors => lambda { puts ">8" }
  }

  # bart's strategy
  throw[:rock].call()

  #random throw
  throw.values[rand(3)].call()

  #adding a throw
  throw[:dynamite] = lambda { puts "[]~" }
  throw[:dynamite].call()
Mario Menger
+1  A: 

Python using classes

class Dispatcher(object):
    def rock(self):
        print 'o'
    def paper(self):
        print '~'
    def scissors(self):
        print '8<'

dispatcher = Dispatcher()
result = getattr(dispatcher, 'rock')
result()
Steven Degutis
A: 

Delphi

Using Generics

program RPS;

uses
  SysUtils,
  Generics.Collections;

type
  TDispatchTable = class(TDictionary<string, TProc>);

procedure Rock;
begin
end;

procedure Paper;
begin
end;

procedure Scissors;
begin
end;

var
  DispatchTable: TDispatchTable;

begin
  DispatchTable := TDispatchTable.Create;
  try
    DispatchTable.Add('Rock', Rock);
    DispatchTable.Add('Paper', Paper);
    DispatchTable.Add('Scissors', Scissors);

    DispatchTable['Rock'].Invoke; // or DispatchTable['Rock']();
  finally
    DispatchTable.Free;
  end;
end.
codeelegance