RFCP logo

REBOL for COBOL programmers

REBOL lookup tables

This document explains how to do just on thing with REBOL, which is
to make a table for looking up some data value based on some other
data value. Examples might be a looking up a description based on
a code, an address for a name, an allowed value for some input field
on a window. This is a common "data processing" operation.

These lookup tables could be built at run time by reading a file or
a database, or they could be pre-built and reside on disk as text
files. Examples will be shwon for either way, which are essemtially
the same ways thanks to the REBOL "load" and "save" functions.

Contents:

1. Target audience and references
2. The central function, "select"
3. Getting table data

1. Target audience and references

The target audience is a programmer who has to solve a problem that could be solved by looking up something in a table, and who is new enough to REBOL that he doesn't quite know how to start to solve that problem.

Because this article is so specific, it assumes that the reader knows how to write and run REBOL scripts and is just tryihg to find out how to do this one operation, namely, create and use a table for looking up values.

References

"A Micro Database" from the REBOL cookbook

Article about getting data out of files

Article about getting data out a database

Nick Antonaccio's definitive guide about doing useful stuff with REBOL

2. The central function, "select"

Like other programming languages, REBOL has the concept of storing one thing after another. Some might call it an array, REBOL calls it a series. The kind of series relevant to this topic is the "block" series where one thing after another is stored in a block. Inside of memory, a block has its own structure, but when hard-coded into a source program the elements of a block are put in the square brackets.

["A" "Active" "I" "Inactive" "N" "New" "U" "Used" "D" "Discontinued"]

REBOL uses a system of calling functions and getting back results. You code the name of the function, the function is executed, and a returned result can be used in some way.

Putting together the above two points leads to a way to make lookup tables when you know the key function for making that work, which is "select."

The "select" function uses a supplied argument and searches a block for it. Then it returns the item in the block immediately after the found item, or "none" if the item is not found. A value of "none" creates a "false" result when it is the subject of the "if" or "either" functions. For example:

REBOL [
    title: "Test harness"
]
DESCRIPTION-TABLE:  [
    "A" "Active" 
    "I" "Inactive" 
    "N" "New" 
    "U" "Used" 
    "D" "Discontinued"
]
print select DESCRIPTION-TABLE "A"
print select DESCRIPTION-TABLE "I"
print select DESCRIPTION-TABLE "N"
print select DESCRIPTION-TABLE "U"
print select DESCRIPTION-TABLE "D"
print select DESCRIPTION-TABLE "X"
halt

The above example produces:

Active
Inactive
New
Used
Discontinued
none
>>

Notice that you can't have a situation where the code and its description are the same. Or, you could, but it wouldn't work quite right and it would make no sense to do that.

We can take advantage of the fact that if an item is not found "none" is returned. Many times when you search a table you want to pull out the thing you have found and do something with it. If you don't find what you are looking for, you want to do something else, like maybe do something to indicate that the value you searched for is an undefined value.

REBOL [
    title: "Test harness"
]
DESCRIPTION-TABLE:  [
    "A" "Active" 
    "I" "Inactive" 
    "N" "New" 
    "U" "Used" 
    "D" "Discontinued"
]
GET-DESCRIPTION: func [
    STATUS-CODE
] [
    if not DESCRIPTION: select DESCRIPTION-TABLE STATUS-CODE [
        DESCRIPTION: copy "Undefined"
    ]
    return DESCRIPTION
]
print ["A is " GET-DESCRIPTION "A"]
print ["X is " GET-DESCRIPTION "X"]
halt

The above produces:

A is  Active
X is  Undefined
>>

In the function, notice the colon after DESCRIPTION. What happens in that line of code is that the "select" function returns a value, or none, and the word DESCRIPTION is set to refer to it. Then when DESCRIPTION is used with the "if" function, a valid value produces true, and a "none" value produces false. If we get false, we assign the text "Undefined" to description.

The same technique works if a table has more than one value for the key item. In that case, "more than one item" can be put in a block, as in this example of a table of customer service representatives identified by a code number which can be used to obtain their names and telephone extensions.

REBOL [
    title: "Test harness"
]
TECHNICIAN-TABLE:  [
    "21" ["Adam" "1001"]
    "22" ["Bob" "1002"]
    "23" ["Charles" "1003"]
]
TECHNICIAN-CODE: "22"
either TECHNICIAN-BLOCK: select TECHNICIAN-TABLE TECHNICIAN-CODE [
    TECHNICIAN-NAME: copy TECHNICIAN-BLOCK/1
    TECHNICIAN-EXTENSION: copy TECHNICIAN-BLOCK/2
] [
    TECHNICIAN-NAME: copy ""
    TECHNICIAN-EXTENSION: copy ""
]
print TECHNICIAN-NAME
print TECHNICIAN-EXTENSION
halt

The above produces:

Bob
1002
>>

The "select" function returns whatever is after the item searched for, which in this case is a block. The items in the block can be referenced with the "path" notation.

3. Getting table data

It usually is not the best idea to hard-code data into source code. How do we get table data into memory so it can be searched? The easiest way, if you have a small table and can live without a graphical interface, is to type it into a file with a text editor. The REBOL "load" function will allow you to bring in that data with one line of code. To make a test file for the following example, run this script:

REBOL [
    title: "Test harness"
]
CODE-TABLE: {"A" "Active"
"I" "Inactive" 
"N" "New" 
"U" "Used" 
"D" "Discontinued"}
write %testtable.txt CODE-TABLE
halt

If you list the output file with a text editor, it will look like this:

"A" "Active"
"I" "Inactive" 
"N" "New" 
"U" "Used" 
"D" "Discontinued"

With the REBOL "load" function, you can bring that file into memory and the results will be put into a block. Then you will have the same thing as produced above by hard-coding the table in the source code.

REBOL [
    title: "Test harness"
]
CODE-TABLE: load %testtable.txt
probe type? CODE-TABLE
probe CODE-TABLE/1
probe CODE-TABLE/2
probe CODE-TABLE/3
probe CODE-TABLE/4
probe CODE-TABLE/5
probe CODE-TABLE/6
print select CODE-TABLE "I"
halt

The above produces:

block!
"A"
"Active"
"I"
"Inactive"
"N"
"New"
Inactive
>>

Not everyone is a believe in the simplicity of text files. A spreadsheet is a popular if misused data storage device, and the contents can be exported to a CSV (Comma-Separated Value) file. Run the following script to make a test file.

REBOL [
    title: "Test harness"
]
CSR-TABLE: {21,Adam,1001
22,Bob,1002
23,Charles,1003
24,Donald,1005}
write %testtable.csv CSR-TABLE
halt

The resulting file will look like this:

21,Adam,1001
22,Bob,1002
23,Charles,1003
24,Donald,1005

A file like the above can be read, taken apart, reassembled into a block in memory, and then used as a lookup table. Depending on how stable the data is, this operation could be done at run time every time the program is run, or it could be donw once, the resulting block could be saved to a file with the "save" function, and then the saved file could be reloaded at every run time with the "load" function. The "load" and "save" functions are companion functions for writing out and bringing in data in a format the REBOL can make use of.

To make use of the data in the CSV file, we have to do a little work. We will read the file into memory as a block of lines. Then, for each line, we will divide it up based on the commas. The result of that division will be a block of strings, one string for each field scanned based on the commas. Then, for each of those fields, we will append them to an ever-increasing block that will become the final lookup table. In this sample case, the "key" value of the table is going to be that two-digit number, and the "attributes" of the key value are going to be a block consisting of the name and extension, like the earlier example. Make note that when we append a block to the lookup table, we will have to use the "append/only" function to get that block to remain a block.

The script below shows how you might take apart the CSV file. Note the use of the "reduce" function to cause the words in the following block to be evaluated and replaced with their values.

REBOL [
    title: "Test harness"
]
CSR-TABLE: copy []
TABLE-CSV: read/lines %testtable.csv
foreach TABLE-LINE TABLE-CSV [
    FIELD-BLOCK: copy []
    FIELD-BLOCK: parse TABLE-LINE ","
    append CSR-TABLE FIELD-BLOCK/1
    append/only CSR-TABLE reduce [FIELD-BLOCK/2 FIELD-BLOCK/3]
]
probe type? CSR-TABLE
probe CSR-TABLE/1
probe CSR-TABLE/2
probe CSR-TABLE/3
probe CSR-TABLE/4
probe CSR-TABLE/5
probe CSR-TABLE/6
probe select CSR-TABLE "24"
;; Optinoally,...
save %testtable.txt CSR-TABLE
halt

The above produces:

block!
"21"
["Adam" "1001"]
"22"
["Bob" "1002"]
"23"
["Charles" "1003"]
["Donald" "1005"]
>>

If you list the saved file, you see that the saved data is all on one line.

"21" ["Adam" "1001"] "22" ["Bob" "1002"] "23" ["Charles" "1003"] "24" ["Donald" "1005"]

The line-feed characters are white space to the "load" and "save" functions, it seems. If you had typed the data by hand, you could have put it all on one line, or you could have put each table on its own line for readability, like we did in the earlier examples.

If the data you want to put into a lookup table is elsewhere, you can use the above techniques as long as you can get your hands on the data. On common location is a database accessible with SQL. If you can write a query to get the data and then can read through the fields you obtain, you can append them to a block as shown above and use the resulting block for a lookup table. Unfortunately, we don't have a common database so it is not possible to show here an example of that.