Way Too Many IF-THEN-ELSEs?  Try a Lookup Table
Tags: programming, perl, tutorial, algorithms
Published: 2021-02-28 01:33:33

This article illustrates a use case where a simple-to-implement lookup table can be used as a better alternative to hard coding decision logic.  The coding trick used here should be applicable to any programming language with multi-dimensional associative arrays.

## When would you want to do this? ##

A problem I've faced more than a few times is having a set of variables, perhaps aggregating data from various sources, and needing to determine what overall state, from a limited set of valid states, is being represented by those values.  My first cut at the problem would probably be to draw a decision tree, considering each variable in turn, and then try to implement that tree's logic using nested if-then-else and/or case (switch) statements.  Depending upon the scope and nature of the problem, this could result in a lot of logic that may be hard to follow, debug, or extend.

One possible alternative is to create a lookup table that enumerates all the expected values for each variable, and to assign the desired state represented by each row of that table.  Creating the table may be a bit tedious, but it can be more easily understood by non-coders, and the logic for doing the lookup can be near trivial.  And perhaps the biggest win is the simplicity in adding, removing, and modifying states by updating the table, rather than hacking on the logic.

Let's consider a simple example; ordering a car where there are five different trim lines and a set of options.  Not all options are available for all trim lines.  Here's what that might look like:

<table border=0>
<tr>
  <th align="center">Trim</td>
  <th align="center">Interior</td>
  <th align="center">Wheels</td>
  <th align="center">Transmission</td>
  <th align="center">Cruise</td>
</th>
<tr>
  <th align="right">Base</th>
  <td align="center">cloth</td>
  <td align="center">steel | alloy</td>
  <td align="center">manual</td>
  <td align="center">n/a</td>
</tr>
<tr>
  <th align="right">Premium</th>
  <td align="center">cloth | leather</td>
  <td align="center">alloy</td>
  <td align="center">manual | auto</td>
  <td align="center">n/a</td>
</tr>
<tr>
  <th align="right">Sport</th>
  <td align="center">sport-cloth</td>
  <td align="center">18" black alloy</td>
  <td align="center">6 speed manual</td>
  <td align="center">opt</td>
</tr>
<tr>
  <th align="right">Limited</th>
  <td align="center">cloth | leather</td>
  <td align="center">alloy</td>
  <td align="center">manual | auto</td>
  <td align="center">std</td>
</tr>
<tr>
  <th align="right">Touring</th>
  <td align="center">leather</td>
  <td align="center">18" alloy</td>
  <td align="center">auto</td>
  <td align="center">std</td>
</tr>
</table>

We've been tasked to write a function that determines if a set of dealer selected options are allowed for a given trim line, and to identify a valid combination of options and trim as a particular 'bundle' identifier.  Our first step will be to expand the table so that all possible valid combinations are enumerated, and we'll simplify the identifiers a bit.

<table border=0>
<tr>
  <th align="center">Trim</td>
  <th align="center">Interior</td>
  <th align="center">Wheels</td>
  <th align="center">Transmission</td>
  <th align="center">Cruise</td>
  <th align="center">Bundle</td>
</th>
<tr>
  <th align="right">Base</th>
  <td align="center">cloth</td>
  <td align="center">steel</td>
  <td align="center">manual</td>
  <td align="center">0</td>
  <td align="center">B1</td>
</tr>
<tr>
  <th align="right">Base</th>
  <td align="center">cloth</td>
  <td align="center">alloy</td>
  <td align="center">manual</td>
  <td align="center">0</td>
  <td align="center">B2</td>
</tr>
<tr>
  <th align="right">Premium</th>
  <td align="center">cloth</td>
  <td align="center">alloy</td>
  <td align="center">manual</td>
  <td align="center">0</td>
  <td align="center">P1</td>
</tr>
<tr>
  <th align="right">Premium</th>
  <td align="center">leather</td>
  <td align="center">alloy</td>
  <td align="center">manual</td>
  <td align="center">0</td>
  <td align="center">P2</td>
</tr>
<tr>
  <th align="right">Premium</th>
  <td align="center">cloth</td>
  <td align="center">alloy</td>
  <td align="center">auto</td>
  <td align="center">0</td>
  <td align="center">P3</td>
</tr>
<tr>
  <th align="right">Premium</th>
  <td align="center">leather</td>
  <td align="center">alloy</td>
  <td align="center">auto</td>
  <td align="center">0</td>
  <td align="center">P4</td>
</tr>
<tr>
  <th align="right">Sport</th>
  <td align="center">sport</td>
  <td align="center">sport</td>
  <td align="center">sport</td>
  <td align="center">0</td>
  <td align="center">S1</td>
</tr>
<tr>
  <th align="right">Sport</th>
  <td align="center">sport</td>
  <td align="center">sport</td>
  <td align="center">sport</td>
  <td align="center">1</td>
  <td align="center">S2</td>
</tr>
<tr>
  <th align="right">Limited</th>
  <td align="center">cloth</td>
  <td align="center">alloy</td>
  <td align="center">manual</td>
  <td align="center">1</td>
  <td align="center">L1</td>
</tr>
<tr>
  <th align="right">Limited</th>
  <td align="center">leather</td>
  <td align="center">alloy</td>
  <td align="center">manual</td>
  <td align="center">1</td>
  <td align="center">L2</td>
</tr>
<tr>
  <th align="right">Limited</th>
  <td align="center">cloth</td>
  <td align="center">alloy</td>
  <td align="center">auto</td>
  <td align="center">1</td>
  <td align="center">L3</td>
</tr>
<tr>
  <th align="right">Limited</th>
  <td align="center">leather</td>
  <td align="center">alloy</td>
  <td align="center">auto</td>
  <td align="center">1</td>
  <td align="center">L4</td>
</tr>
<tr>
  <th align="right">Touring</th>
  <td align="center">leather</td>
  <td align="center">touring</td>
  <td align="center">auto</td>
  <td align="center">1</td>
  <td align="center">T1</td>
</tr>
</table>

When we are convinced that we have enumerated all of the valid possible combinations, we can turn this table into a multi-dimensional hash.  The resulting Perl code, shown below, bears an uncanny resemblance to our table.

```perl
%bundle = ();
#       Trim      Interior  Wheels    Trannie  Cruise Bundle
#       --------- --------- --------- -------- ------ ------
$bundle {base}    {cloth}   {steel}   {manual} {0}   = 'B1';
$bundle {base}    {cloth}   {alloy}   {manual} {0}   = 'B2';
$bundle {premium} {cloth}   {alloy}   {manual} {0}   = 'P1';
$bundle {premium} {leather} {alloy}   {manual} {0}   = 'P2';
$bundle {premium} {cloth}   {alloy}   {auto}   {0}   = 'P3';
$bundle {premium} {leather} {alloy}   {auto}   {0}   = 'P4';
$bundle {sport}   {sport}   {sport}   {sport}  {0}   = 'S1';
$bundle {sport}   {sport}   {sport}   {sport}  {1}   = 'S2';
$bundle {limited} {cloth}   {alloy}   {manual} {1}   = 'L1';
$bundle {limited} {leather} {alloy}   {manual} {1}   = 'L2';
$bundle {limited} {cloth}   {alloy}   {auto}   {1}   = 'L3';
$bundle {limited} {leather} {alloy}   {auto}   {1}   = 'L4';
$bundle {touring} {leather} {touring} {auto}   {1}   = 'T1';
```

Now to determine if we have been given a valid combination of trim and options, and to return the bundle code, we just do:

```perl
$trim   = 'touring';
$seats  = 'leather';
$wheels = 'touring';
$trans  = 'auto';
$cruise = 1;

$bCode = $bundle{$trim}{$seats}{$wheels}{$trans}{$cruise};
```

If `$bCode` is defined, then we know the combination is valid and we have our bundle code (`T1`).

Notice how adding a new trim line or a new option is just a matter of modifying the table with little or no change to the lookup statement.  In contrast, a block of if-then-elsif statements might not be so easy to hack and debug.

An interactive command-line demo script, which also includes error handling and validation, can be [found here](https://gist.github.com/Bill-Costa/314d6162bb623675de17a575802af441).