(NOTE: The prefix 'list' may be used interchangeably here with 'table'. Thus, 'look-up list' and 'look-up table' are describing the same thing.)
Look-up tables, are listings of key:value
pair items allowing simple value arrays (more formally called a one-dimensional array).
See also: Dictionary-type attributes
Since look-up tables were added, Tinderbox added the Dictionary data type. It too holds key:value pairs but Dictionary-type attribute offer extra affordances for creating/editing data via action code.
In many cases, if starting from new, a Dictionary may well be a better choice than a List for this purpose.
Look-up table functional example
For those without a coding background, this is more easily understood by use of a simple example. Thus, suppose some notes record the US state (in attribute $State) in which each customer resides. Each state is also assigned to on of a number of a regions and it is useful to know the sales region ($Region) for given customer's home $State. Before support for look-up tables, a usual approach—to avoid hard coding each note's $Region—would be via use of conditional if() statements:
if($State=="AL") { $Region = "South";}
However, this is a tedious process if there are many states as it needs a lot of if() statements, at least 50 in this case!
To use a look-up table for mapping $State to $Region, we make just one listing of colon-joined key:value
pairs, separated by colons, thus: Key1:Value1;Key2:Value2. For example:
$RegionList=[AL:South;AK:NorthWest]
Several data types can be used to store look-up tables, and that aspect of use is discussed further below
Limitations on key and value definition
A key is:
- case-sensitive
- must be unique to the listing
- ought not to consist of numbers only as this may confuse Tinderbox (is it a key value or the item number in the list?)
- should there be a need to use numbered keys, e.g. if generating the look-up key based on a computed value, place a letter before the number. So, use 'x1' rather than just '1'.
A value may be the same as values for different keys within the current listing.
Accessing look-up table values
Look-up tables are interrogated using the .lookup("key") operator like so:
$RegionListing=[AL:South;AK:NorthWest];
$Region=$RegionListing.lookup("AL");
sets a $Region value of "South".
Optional default value for missing keys
If the look-up table does not contain the supplied key value, .lookup() then looks for an optional special default key (default
, case-sensitive) and returns that key's value or if none is found, an empty string is returned (if the 'default' key exists but has no value, an empty string is returned). So:
$RegionListing=[AL:South;AK:NorthWest];
$Region=$RegionList.lookup("CT");
gives a nil value of "" as there is no matching key 'CT'. If the option 'default' key is added to the look-up table:
$RegionList=[AL:South;AK:NorthWest;default:Unknown];
Then
$Region=$RegionList.lookup("CT");
sets $Region to "Unknown".
The key input argument can be evaluated. Thus if attribute $State has a value "AL", then:
$Region=$RegionList.lookup($State);
sets a $Region value of "South".
Alternate keys, same value
Lookup tables are able to specify several keys that map to a common value. This is done by separating each key with the pipe character '|'. For example:
[Oliver|Micawber|Pip:Dickens;Tess:Hardy;Palliser|Finn:Trollope;default:anon]
A structural representation of the above example is as follows, with each key:value pairing on a separate line for clarity.
Key1-1|Key1-2|Key1-3:Value1;
Key2-1:Value2;
Key3-1|Key3-2:Value3;
default:anon
The number of keys in each Key/Value set is discrete from other sets in the list: 3 keys map to Value1, one to Value2, two to Value 3 and default has one value..
Keys are matched on exact, case-sensitive, strings. In the above example "Tess" will return "Hardy" but "tess", "Tes" or "Tessa" would return "anon". If no default had been defined, an empty string "" would be returned instead for the non-matching values.
Lexical and numerical ranges for keys, same value
Lookup tables also permit ranges (based on case-sensitive lexical sort based matching). For example, suppose the value of $MyList is:
[Alaska-Connecticut: 1; Delaware-Nebraska: 2; default: 3]
Then $MyList.lookup("Alabama")
would return string "1", and $MyList.at("Illinois")
would return "2". However, $MyList.lookup("alabama")
returns "3" due to the way Tinderbox lexical sort works, as would. Note too, that in this case $MyList.lookup("Manitoba")
would return "2" as "Manitoba" lexically sorts between "Delaware" and "Nebraska": Tinderbox has no way to know Manitoba is a Canadian province rather than a US state.
Lookup tables can also work with numeric ranges. If $MyList is:
[0-10: red; 10-20: blue; default: green]
Then $MyList.lookup(5.0)
returns 'red'. Note that $MyList.lookup(5)
(N.B. an integer without a decimal point matches the key "5". Thus if the actual key is "5" use "5.0" to trigger the first behaviour.
What data type to use for storing look-up tables?
When the feature of look-up tables was first added, the Dictionary data type did not exist, so a List or Set was suggested. Sets, make more sense than lists are they avoid unintentional duplicates. Legacy: not that prev9.5.0 using List.unique() to de-duplicate a list re-sorts that list. As a result either a Set or Dictionary type attribute is suggested for holding a look-up table. List type may also be used, but attention should be given to accidental key duplication.
For Dictionary type, as long as used with .lookup() rather than the normal Dictionary["key"] access methods, a Dictionary supports all the above advanced look-up table behaviours (alternate keys, key ranges).
Storing the look-up table in one location
In the first above example, the look-up table listing is stored in the note being tested, for simplicity of explanation and learning. But, in reality, this same list may need to be used in hundreds of different notes. Luckily this is easily done by defining the look-up list as a global value, once, in a single note and then referenced from all others by an offset attribute reference, i.e. using $AttributeName(name) or $AttributeName(path). If using this technique chose a unique note $Name, you will need to use the full $Path on the offset.
Thus these examples are more representative of real-world use:
$Region=$RegionListing("config").lookup("AL")
$Region=$RegionListing("config").lookup($State)
Noting the point about the name of the note, it may make more sense to use a deliberately unique name that also implies the note's purpose. So:
$Region=$RegionList("lt_States").lookup("AL")
A note called 'lt_States' ('lt' for 'look-up table'), will stand out in action code and imply a look-up table is being referenced and when revisited months later the note's $Name may remind us as to its purpose.