5. Hashes

Introduction

/* ------------------------------------------------------------------ */
/* Where Perl implements 'hashes', REXX offers 'compound variables' to*/
/* fulfil the same role [they are each, after all, associative array  */
/* implementations]. Additionally, it is important to understand that */
/* compound variables are not simply another data structure, but a key*/
/* REXX feature - many REXX idioms involve compound variable use. For */
/* example, external library functions such as those from the 'rxSock'*/
/* library [i.e. TCP/IP sockets] use compound variables to exchange   */
/* multi-valued data such as host name/address and port number aggre- */
/* gations. Another use is the making available the lines of a file as*/
/* compound variable 'leaves', where, for example, the five lines read*/
/* from 'infile.txt' into a compound variable called 'infile.', would */
/* be accessable as, 'infile.1' through 'infile.5'; each 'leaf' is    */
/* named after the corresponding line number. Note also that the term */
/* 'stem variable' is sometimes used to describe compound variables.  */
/*                                                                    */
/* By adopting certain design conventions, compound variables may be  */
/* used to build high-level data structures like lists and trees,     */
/* build 'associations' between items [i.e. property lists], as well  */
/* as mimic data structures like records / structures, and also single*/
/* and multi-dimensional arrays [both numerically and non-numerically */
/* indexed]. However, it will be their use as 'hashes' that will be   */
/* disccussed here.                                                   */
/*                                                                    */
/* Whilst compound variables are quite versatile, there are a number  */
/* of quirks associated with their use:                               */
/*                                                                    */
/* * Indexing is via the '.' operator. This usage can be confusing for*/
/*   those used to using this operator to access structure members [as*/
/*   in C or Java]. Furthermore, the '[]' operator, commonly used as  */
/*   an array index operator, is not available [though it is in more  */
/*   recent REXX implementations like ooREXX]                         */
/*                                                                    */
/* * They cannot be passed to, nor returned from, REXX subroutines, by*/
/*   reference [though it is possible to pass the name of such items  */
/*   and access their contents indirectly via the VALUE BIF]. It is,  */
/*   however, possible to pass the name of a compound variable to an  */
/*   external library function and have it access and/or 'fill' it    */
/*   with data                                                        */
/*                                                                    */
/* * Index names must either be literals, or variables; expressions   */
/*   cannot be used [they would need to be assigned to a variable, and*/
/*   the variable used for indexing]                                  */
/*                                                                    */
/* * There is no instruction or BIF for traversing compound variable  */
/*   members. In order to allow such an operation, it is typical to:  */
/*                                                                    */
/*   - Use numerically-based indexes [index '0' has total items] e.g. */
/*                                                                    */
/*     cv.1 = "first" ; cv.2 = "second" ; cv.3 = "third" ; cv.0 = 3   */
/*                                                                    */
/*     do i = 1 to cv.0                                               */
/*       say cv.i                                                     */
/*     end                                                            */
/*                                                                    */
/*   - Use a purpose-built, external library function. 'regStemDoOver'*/
/*     from the *NIX / Win32 implementation of the 'rexxUtil' library */
/*     is one such function                                           */
/*                                                                    */
/* * It is not possible to manually sort or merge compound variables  */
/*   unless numerically-based indexes are used [or purpose-built, ext-*/
/*   ernal library function used]. Similarly, certain external library*/
/*   functions [e.g. 'sysStemInsert', 'sysStemDelete' etc] will only  */
/*   work with compound variables following this convention           */
/*                                                                    */
/* Owing to these quirks, it is common to see use made of compound    */
/* variables with numerically-based indexes where there is a need for */
/* non key-based searching, or where data needs to be sorted. Also, a */
/* compound variable will tend to be shared among subroutines, usually*/
/* via the 'EXPOSE' instruction, in the same manner as a global value.*/
/*                                                                    */
/* Several examples make use of 'rexxUtil' library functionality, so  */
/* assume the existence of the following prologue code:               */
/*                                                                    */
/*     call rxFuncAdd 'sysLoadFuncs', 'rexxUtil', 'sysLoadFuncs'      */
/*     call ssyLoadFuncs                                              */
/*                                                                    */
/* and the following epilogue code [usually at program's end]:        */
/*                                                                    */
/*     call sysDropFuncs                                              */
/* ------------------------------------------------------------------ */

/* Create compound variable, 'age.', uninitialised variables as keys */

age.Nat = 24                         /* Equivalent to: age."NAT" */
age.Jules = 24                       /* ... age."JULES" */
age.Josh = 17                        /* ... age."JOSH" */

/* ----------------------------- */

/* As previous; used from subroutine when stem name runtime-supplied */
stem = "age."
key = "Nat" ; call VALUE stem||key, 24
key = "Jules" ; call VALUE stem||key, 24
key = "Josh" ; call VALUE stem||key, 17

/* ----------------------------- */

/* Preferred approach [key placed in variable] as keys retain case */
key = "Nat" ; age.key = 24
key = "Jules" ; age.key = 24
key = "Josh" ; age.key = 17

/* As previous, except key-value pairs parsed from list */
name_list = "Nat 24 Jules 24 Josh 17"
do while name_list <> NULL
  parse var name_list key age.key name_list
end

/* ----------------------------- */

key = "Apple" ; food_color.key = "red"
key = "Banana" ; food_color.key = "yellow"
key = "Lemon" ; food_color.key = "yellow"
key = "Carrot" ; food_color.key = "orange"

/* As previous, except key-value pairs parsed from list */
food_list = "Apple red Banana yellow Lemon yellow Carrot orange"
do while food_list <> NULL
  parse var food_list key food_color.key food_list
end

Adding an Element to a Hash

/* ------------------------------------------------------------------ */
/* The rules for leaf [i.e. element, key / value] addition are simple:*/
/*                                                                    */
/* * If a leaf exists, then its content is replaced with the new value*/
/*                                                                    */
/* * If a leaf does not exist, then a new leaf is created, and the new*/
/*   value stored there                                               */
/*                                                                    */
/* The external library function, 'sysStemInsert', may also be used   */
/* provided the compound variable uses numerically-based indexes.     */
/* ------------------------------------------------------------------ */

key = "...key..." ; mydict.key = "...value..."

/* ----------------------------- */

key = "Raspberry" ; food_color.key = "pink"

/* Traverse compound variable using external ['rexxUtil'] function */
say "Known foods:"
do while regStemDoOver('food_color.', 'food')
  say food "is" food_color.food
end

Testing for the Presence of a Key in a Hash

/* ------------------------------------------------------------------ */
/* The built-in function [BIF], 'SYMBOL', is used to check whether a  */
/* particular leaf / key already exists. Note, however, effective use */
/* of this technique requires that the compound variable *not* be set */
/* to default values via:                                             */
/*                                                                    */
/*     cv. = DEFAULT_VALUE                                            */
/*                                                                    */
/* otherwise a 'SYMBOL' call will always indicate that the leaf / key */
/* exists even if it has not been explicitly added. In such cases the */
/* testing for leaf / key presence sees a content comparision with the*/
/* default value:                                                     */
/*                                                                    */
/*     if cv.key \= DEFAULT_VALUE then ; say "Key defined"            */
/*     else ; say "No such key defined"                               */
/*                                                                    */
/* In other words [and as illustrated below] there is no distinction  */
/* between an 'exists' test and a 'defined' test when a compound var- */
/* iable is default-valued. This is a behaviour which can easily trap */
/* the unwary.                                                        */
/* ------------------------------------------------------------------ */

/* Does 'mydict' have a value for KEY ? */

key = "..."

if SYMBOL('mydict.key') == 'VAR' then
  /* Key exists */
  nop
else
  /* No such key */
  nop

/* ----------------------------- */

food_list = "Banana Martini"

/* 1: Parse list using WORD-based BIF's and counted loop */
numberOfWords = WORDS(food_list)

do i = 1 for numberOfWords
  name = WORD(food_list, i)
  if SYMBOL('food_color.name') == 'VAR' then
    say name "is a food."
  else
    say name "is a drink."
end

/* 2: Parse list using PARSE instruction with conditional loop */
do while food_list <> NULL
  parse var food_list name food_list
  if SYMBOL('food_color.name') == 'VAR' then
    say name "is a food."
  else
    say name "is a drink."
end

/* ----------------------------- */

drop age.

key = "Toddler" ; age.key = 3
key = "Unborn" ; age.key = 0
key = "Phantasm" ; age.key = NULL

do i = 1
  thing = WORD("Toddler Unborn Phantasm Relic", i)
  if thing = NULL then ; leave
  call CHAROUT , thing||": "
  if SYMBOL('age.thing') == 'VAR' then do
    call CHAROUT , "Exists "
    if age.thing \= NULL then ; call CHAROUT , "Defined"
  end
  call LINEOUT , NULL
end

/* ----------------------------- */

/* Read a file-based list of filenames; store their sizes in a hash */
do forever
  parse value STRIP(LINEIN("filelist.txt")) with filename
  if filename == NULL then ; leave
  if SYMBOL('size.filename') \= 'VAR' then
    size.filename = STREAM(filename, 'C', "QUERY SIZE")
end

Deleting from a Hash

/* ------------------------------------------------------------------ */
/* The DROP instruction may be used both to:                          */
/*                                                                    */
/* * Remove a specific leaf / key [e.g. drop cv.key]                  */
/*                                                                    */
/* * Destroy the entire compound variable [e.g. drop cv.]             */
/*                                                                    */
/* The external library function, 'sysStemDelete', may also be used   */
/* provided the compound variable uses numerically-based indexes.     */
/* ------------------------------------------------------------------ */

/* Remove KEY and its value from 'mydict' */

key = "...key..." ; drop mydict.key

/* ----------------------------- */

say "Intially"
call print_foods

say "With Banana set to None"
key = "Banana" ; food_color.key = NULL
call print_foods

say "With Banana deleted"
key = "Banana" ; drop food_color.key
call print_foods

exit 0

/* ----------------------------- */

print_foods : procedure expose food_color. (globals)
  keys = NULL ; values = NULL

  do while regStemDoOver('food_color.', 'food')
    keys = keys food
    if food_color.food \= NULL then
      values = values food_color.food
    else
      values = values "undef"
  end

  say "Keys:  " keys
  say "Values:" values

  return

/* ----------------------------- */

key = "Banana" ; drop food_color.key
key = "Apple" ; drop food_color.key
key = "Cabbage" ; drop food_color.key

/* As previous, except keys parsed from list */
key_list = "Banana Apple Cabbage"
do while key_list <> NULL
  parse var key_list key key_list
  drop food_color.key
end

Traversing a Hash

/* ------------------------------------------------------------------ */
/* Compound variable traversal may be accomplished two ways:          */
/*                                                                    */
/* * Via 'do ... end' instruction if the compound variable follows the*/
/*   numeric index convention [i.e. cv.0 = N ; elements: cv.1 .. cv.N]*/
/*                                                                    */
/* * Via external library function like 'rexxUtil's 'regStemDoOver',  */
/*   which may be used to traverse *any* compound variable, or create */
/*   a numerically indexed copy of an existing compound variable      */
/* ------------------------------------------------------------------ */

cv.0 = N                    /* Number of data items */
cv.1 = "..." ; cv.N = "..." /* Data */

do i = 1 for cv.0
  /* do something with index and value */
  say i "==>" cv.i
end

/* ----------- */

do while regStemDoOver('compound_variable_name.', 'key')
  /* do something with key and value */
  say key "==>" compound_variable_name.key
end

/* ----------------------------- */

/* Numeric index-based compound variable traversal */

cv.0 = 5 /* Number of data items */
cv.1 = 23 ; cv.2 = 734 ; cv.3 = 152 ; cv.4 = 876 ; cv.5 = 91  /* Data */

do i = 1 for cv.0
  say "Element" i "contains" cv.i
end

/* ----------- */

/* Same using external ['rexxUtil'] function [order not guaranteed] */

do while regStemDoOver('cv.', 'i')
  if i == "0" then ; iterate /* Exclude 'cv.0' */
  say "Element" i "contains" cv.i
end

/* ----------------------------- */

/* Non-numeric index-based traversal [order not guaranteed] */

say "Known foods:"
do while regStemDoOver('food_color.', 'food')
  say food "is" food_color.food
end

/* ----------- */

/* Non-numeric index-based *ordered* traversal */

/* 1: Create numeric index-based compound variable of *keys* */
do i = 1 while regStemDoOver('food_color.', 'food')
  idx_food_color.i = food ; idx_food_color.0 = i
end

/* 2: Sort the new compound variable */
call sysStemSort 'idx_food_color.'

/* 3: Traverse key-sorted compound variable; use lookup for values */
do i = 1 for idx_food_color.0
  key = idx_food_color.i
  say "Element" i "contains" LEFT("[" || key || "]", 8) " ==>",
      food_color.key
end

Printing a Hash

/* ------------------------------------------------------------------ */
/* Printing a compound variable is merely a special case of traversal */
/* therefore all examples shown in the previous section apply.        */
/* ------------------------------------------------------------------ */

/* *** All examples in previous section apply here also *** */

Retrieving from a Hash in Insertion Order

/* ------------------------------------------------------------------ */
/* Insertion order is not part of a compound variable's metadata. If  */
/* needed, an insertion-order index could be stored in a seperate     */
/* compound variable, most likely a numerically-indexed one. Each     */
/* index would itself indicate insertion order [i.e. .1 before 2. and */
/* so on]. However such metadata could be lost if sorting occurs or   */
/* there are frequent deletions and accompanying reuse of indexes.    */
/* ------------------------------------------------------------------ */

/* Store insertion order metadata in separate compound variable */
key = "Banana" ; food_color.key = "yellow" ; food_color_order.1 = key
key = "Apple" ; food_color.key = "red" ; food_color_order.2 = key
key = "Lemon" ; food_color.key = "yellow" ; food_color_order.3 = key
food_color_order.0 = 3

say "In insertion order, the foods are:"
do i = 1 to food_color_order.0
  say "   " food_color_order.i
end

say "Still in insertion order, the foods' colors are:"
do i = 1 to food_color_order.0
  key = food_color_order.i
  say key "is colored" food_color.key
end

Hashes with Multiple Values Per Key

/* ------------------------------------------------------------------ */
/* Multiple values per leaf / key can easily be accommodated if stored*/
/* as REXX lists [i.e. SPACE-delimited strings]. Related issues:      */
/*                                                                    */
/* * PARSE instruction or WORD BIF's can be used to extract required  */
/*   value                                                            */
/*                                                                    */
/* * Values may simply be appended, or ordered insertions made, the   */
/*   latter useful for search [binary search can be used] or reporting*/
/*   purposes                                                         */
/* ------------------------------------------------------------------ */

cmd = "who" ; address SYSTEM cmd with OUTPUT FIFO ''

do while QUEUED() > 0
  parse pull user tty .

  /* Insert values in ascending order so no sorting later needed */
  if SYMBOL('ttys.user') == 'VAR' then
    ttys.user = insertWord(tty, ttys.user)
  else
    ttys.user = tty
end

do while regStemDoOver('ttys.', 'user')
  say user || ":" ttys.user
end

exit 0

/* ----------------------------- */

insertWord : procedure
  V = ARG(1) ; S = STRIP(ARG(2)) ; R = WORDS(S)
  if R < 1 then ; return V
  L = 1

  do while L <= R
    M = (L + R) % 2 ; W = WORD(S, M)
    if V = W then ; return S
    if V < W then ; R = M - 1 ; else L = M + 1
  end

  /* insert [after] item here */
  select
    when R < 1 then ; insertAfter = 0
    when L > WORDS(S) then ; insertAfter = LENGTH(S) + 1
    otherwise
      if M = R then ; insertAfter = WORDINDEX(S, M + 1) - 1
      else ; insertAfter = WORDINDEX(S, M) - 1
  end

  return INSERT(V, S, insertAfter, LENGTH(V) + 1)

Inverting a Hash

/* ------------------------------------------------------------------ */
/* This task requires that an existing compound variable be traversed */
/* and a new compound variable created in which the contents of each  */
/* of the existing compound variable's entries becomes a key in the   */
/* new compound variable, and the corresponding key, its contents.    */
/*                                                                    */
/* A decision as to how duplicate new keys will be handled is needed. */
/* ------------------------------------------------------------------ */

key = "Mantle" ; surname.key = "Mickey" ; key = "Ruth" ; surname.key =
"Babe"

/*
   Mantle Mickey
   Ruth Babe
*/
do while regStemDoOver('surname.', 'name')
  say name surname.name
end

/* Invert key <==> value */
do while regStemDoOver('surname.', 'name')
  key = surname.name ; firstname.key = name
end

/*
   Mickey Mantle
   Babe Ruth
*/
do while regStemDoOver('firstname.', 'name')
  say name firstname.name
end

/* ----------------------------- */

if ARG() < 1 then do ; say "usage: foodfind food|color" ; exit 1 ; end

given = ARG(1)

key = "Apple" ; color.key = "red"
key = "Banana" ; color.key = "yellow"
key = "Lemon" ; color.key = "yellow"
key = "Carrot" ; color.key = "orange"

/* Invert key <==> value */
do while regStemDoOver('color.', 'food')
  key = color.food ; food.key = food
end

if SYMBOL('color.given') == 'VAR' then
  say given "is a food with color" color.given

if SYMBOL('food.given') == 'VAR' then
  say food.given "is a food with color" given

exit 0

Sorting a Hash

/* ------------------------------------------------------------------ */
/* The only means of sorting [i.e. ordering the leaves or their data] */
/* of a compound variable is to:                                      */
/*                                                                    */
/* * Ensure the compound variable has a numerically-based index struc-*/
/*   ture [i.e. is an NICV; if not, copy data and create one]         */
/*                                                                    */
/* * Use one of:                                                      */
/*                                                                    */
/*   - External library sort routine ['rexxUtil's 'sysStemSort']      */
/*   - Custom-written native REXX sort routine                        */
/*   - External sort utility via the ADDRESS instruction [Regina-only]*/
/*                                                                    */
/* Use of an external library sort routine is the most preferable as  */
/* it not only avoids the need to write such code, but is also the    */
/* most efficient since it is machine code acting directly on data,   */
/* and avoiding the data conversion that would be needed in the case  */
/* of a custom-written native REXX routine. However the latter is the */
/* most flexible since there is the widest choice of sort algorithm   */
/* available.                                                         */
/*                                                                    */
/* Generally speaking, the use of an ADDRESS instruction-based sort   */
/* should be avoided unless a very large [i.e. 1MB or more] amount of */
/* data is being handled, or data is file-based, in which case it is  */
/* a reasonable means of loading data into a compound variable since  */
/* the load / sort step is combined.                                  */
/*                                                                    */
/* Strange as it may seem, REXX does not offer a built-in function    */
/* [BIF] for sorting. This is a legacy of its mainframe origins; in   */
/* such environments data sorting tends to be performed on enormous   */
/* amounts of file-based [rather than memory-resident] data, and is a */
/* task reserved for specialised sorting utilities eg. DFSORT utility */
/* on IBM's MVS operating system.                                     */
/* ------------------------------------------------------------------ */

/* Numerically-indexed compound variable */

cv.0 = N                    /* Number of data items */
cv.1 = "..." ; cv.N = "..." /* Data */

/* ----------- */

/* 1: External Library Sort Routine */

call sysStemSort 'cv.', 'ascending'

/* ----------- */

/* 2: Custom REXX Sort Routine */

/* Version with direct access to, 'cv.' */
call mySortRoutine

/* Generic version which is passed a compound variable name */
call myGenericSortRoutine 'cv.'

/* ----------- */

/* 3: ADDRESS Instruction using Sort Utility [Regina-only] */

cmd = "sort"
address SYSTEM cmd with INPUT STEM cv. OUTPUT REPLACE STEM cv.

/* ----------------------------- */

mySortRoutine : procedure expose cv.
  swp = 1
  do while swp
    swp = 0
    do i = 2 to cv.0
      n = i - 1
      if cv.n > cv.i then ; parse value 1 cv.n cv.i with swp cv.i cv.n
    end
  end
  return

/* ----------------------------- */

myGenericSortRoutine :
  _stm = ARG(1) ; if RIGHT(_stm, 1) \= "." then ; _stm = _stm || "."
  _size = VALUE(_stm||"0") ; _swp = 1
  do while _swp
    _swp = 0
    do _i = 2 to _size
      _n = _i - 1
      if VALUE(_stm||_n) > VALUE(_stm||_i) then do
        _swp = 1 ; _tmp = VALUE(_stm||_i)
        call VALUE _stm||_i, VALUE(_stm||_n) ; call VALUE _stm||_n, _tmp
      end
    end
  end
  drop _stm _size _swp _tmp _n ; return

Merging Hashes

/* ------------------------------------------------------------------ */
/* If 'merging' is defined as the combining of the contents of two or */
/* more compound variables, then there are no restrictions on the type*/
/* of compound variables that can be merged. Each has to be traversed */
/* and their contents placed into another compound variable, and care */
/* taken in how duplicate keys are handled.                           */
/*                                                                    */
/* If merging has to follow some order then the same restrictions     */
/* applicable to sorting compound variables also applies: NICV's -    */
/* numerically-indexed compound variables - must be used.             */
/* ------------------------------------------------------------------ */

a.key1 = "..." ; a.key2 = "..." ; b.keyX = "..." ; b.keyY = "..."

merge_list = "a. b."

do while merge_list <> NULL
  parse var merge_list hash merge_list
  do while regStemDoOver(hash, 'key')
    merged.key = VALUE(hash||"key")
  end
end

/* ----------------------------- */

key = "Apple" ; food_color.key = "red"
key = "Banana" ; food_color.key = "yellow"
key = "Lemon" ; food_color.key = "yellow"

key = "Galleano" ; drink_color.key = "yellow"
key = "Mai Tai" ; drink_color.key = "blue"

drop substance_color.

merge_list = "food_color. drink_color."

do while merge_list <> NULL
  parse var merge_list hash merge_list
  do while regStemDoOver(hash, 'key')
    substance_color.key = VALUE(hash||"key")
  end
end

Finding Common or Different Keys in Two Hashes

/* ------------------------------------------------------------------ */
/* This task is simply a matter of compound variable traversal and key*/
/* existence checking and/or comparison.                              */
/* ------------------------------------------------------------------ */

keys = "k1 k3 k5 k8"
do while keys <> NULL
  parse var keys key keys ; hash1.key = TRUE
end

keys = "k1 k2 k3 k6 k7"
do while keys <> NULL
  parse var keys key keys ; hash2.key = TRUE
end

/* ----------- */

drop common.

do while regStemDoOver('hash1.', 'key')
  if SYMBOL('hash2.key') == 'VAR' then ; common.key = TRUE
end

do while regStemDoOver('common.', 'key')
  say key /* k1, k3 */
end

/* ----------- */

drop this_not_that.

do while regStemDoOver('hash1.', 'key')
  if SYMBOL('hash2.key') \= 'VAR' then ; this_not_that.key = TRUE
end

do while regStemDoOver('this_not_that.', 'key')
  say key /* k5, k8 */
end

/* ----------------------------- */

key = "Apple" ; food_color.key = "red"
key = "Banana" ; food_color.key = "yellow"
key = "Lemon" ; food_color.key = "yellow"

key = "Lemon" ; citrus_color.key = "yellow"
key = "Orange" ; citrus_color.key = "orange"
key = "Lime" ; citrus_color.key = "green"

drop non_citrus.

do while regStemDoOver('food_color.', 'key')
  if SYMBOL('citrus_color.key') \= 'VAR' then ; non_citrus.key = TRUE
end

do while regStemDoOver('non_citrus.', 'key')
  say key /* Apple, Banana */
end

Hashing References

/* ------------------------------------------------------------------ */
/* REXX does not support references; this section is, therefore, not  */
/* applicable. The example using 'files' is, however, implemented to  */
/* illustrate some of REXX's basic file operations.                   */
/* ------------------------------------------------------------------ */

file_list = "/etc/termcap vmunix /bin/cat"

do while file_list <> NULL
  parse var file_list file file_list
  call STREAM file, 'C', 'OPEN READ'
  name.file = TRUE
end

do while regStemDoOver('name.', 'file')
  bytes = STREAM(file, 'C', 'SEEK < READ CHAR')
  say file "is" bytes "bytes long"
  call STREAM file, 'C', 'CLOSE'
end

Presizing a Hash

/* ------------------------------------------------------------------ */
/* Compound variables do not have a 'size', as such. Size is merely a */
/* count of how many leaves exist at a specified time. Thus, size is  */
/* not part of a compound variable's metadata, nor is the concept of  */
/* 'presizing' one that is ordinarily applicable.                     */
/*                                                                    */
/* Of course it is possible to:                                       */
/*                                                                    */
/* * Manually maintain 'size' metadata [e.g. the '.0' leaf in NICV's] */
/*                                                                    */
/* * Add a specified number of leaves to a compound variable, each    */
/*   perhaps containing a value to be interpreted as 'empty'. It is   */
/*   important to note that this can only be performed with NICV's    */
/*   because the key / leaf name must be known in order to add it     */
/* ------------------------------------------------------------------ */

/* 'Pre-size' a numerically-indexed compound variable [NICV] */
hash.0 = required_size

/* Leaves 'hash.1', 'hash.2', through 'hash.required_size' created */
do i = 1 to hash.0
  hash.i = null_value
end

/* ----------------------------- */

/* 'Pre-size' a 512 leaf compound variable */
users.0 = 512

do i = 1 to users.0
  users.i = ""
end

/* ----------- */

/* 'Upsize' to 1000 leaves */
oldsize = users.0 ; users.0 = 1000

do i = oldsize + 1 to users.0
  users.i = ""
end

Finding the Most Common Anything

/* ------------------------------------------------------------------ */
/* Two approaches possible:                                           */
/*                                                                    */
/* * Initialise compound variable leaves to a start value [which makes*/
/*   sure there is always a matching key i.e. a check for existence of*/
/*   leaf / key 'X' is always affirmative]                            */
/*                                                                    */
/* * Check for presence of leaf / key before adding / updating entry  */
/* ------------------------------------------------------------------ */

count. = 0

do while regStemDoOver('array.', 'key')
  element = array.key
  count.element = count.element + 1
end

do while regStemDoOver('count.', 'element')
  say element "=" count.element
end

/* ----------- */

do while regStemDoOver('array.', 'key')
  element = array.key
  if SYMBOL('count.element') == 'VAR' then
    count.element = count.element + 1
  else
    count.element = 0
end

Representing Relationships Between Data

/* ------------------------------------------------------------------ */
/* Relationships can easily be set up through judicious naming of keys*/
/* and compound variable names. The examples in the first part of this*/
/* section exemplify this:                                            */
/*                                                                    */
/*    cv.key ==> father.child ==> father of child is [value]          */
/*                                                                    */
/* Here is a 1:N mapping in which one father has one or more children;*/
/* the 'many' component are keys / leaves, so allowing for traversal  */
/* by children, and the 'one' component extractable via comparision.  */
/*                                                                    */
/* Since keys / leaves must be unique, an inversion of the form:      */
/*                                                                    */
/*    cv.key ==> child.father ==> child of father is [value]          */
/*                                                                    */
/* is not possible since the 'many' component would be lost. However, */
/* if the 'many' component is represented as a string with each new   */
/* item appended, then a mapping of the form:                         */
/*                                                                    */
/*    cv.key ==> children.father ==> children of father are [value]   */
/*                                                                    */
/* is possible.                                                       */
/* ------------------------------------------------------------------ */

key = "Cain" ; father.key = "Adam"
key = "Abel" ; father.key = "Adam"

key = "Seth" ; father.key = "Adam"
key = "Enoch" ; father.key = "Cain"

key = "Irad" ; father.key = "Enoch"
key = "Mehujael" ; father.key = "Irad"

key = "Methusael" ; father.key = "Mehujael"
key = "Lamech" ; father.key = "Methusael"

key = "Jabal" ; father.key = "Lamech"
key = "Jubal" ; father.key = "Lamech"

key = "Tubalcain" ; father.key = "Lamech"
key = "Enos" ; father.key = "Seth"

/* ----------- */

do while LINES() > 0
  father = LINEIN() ; if father == NULL then ; leave
  say father || ":"
  do while regStemDoOver('father.', 'child')
    if father == father.child then ; say "   " child
  end
end

/* ----------------------------- */

/* Flawed inversion of 'father.child' relationship */

do while regStemDoOver('father.', 'child')
  key = father.child ; child.key = child
end

/* 1:N mapping lost; only one child per father */
do while regStemDoOver('child.', 'father')
  say father "begat" child.father
end

/* ----------- */

/* String-based inversion of 'father.child' relationship */

do while regStemDoOver('father.', 'child')
  key = father.child
  if SYMBOL('children.key') == 'VAR' then
    children.key = children.key child
  else
    children.key = child
end

/* 1:N mapping retained */
do while regStemDoOver('children.', 'father')
  if LENGTH(children.father) > 0 then
    list_of_children = CHANGESTR(" ", children.father, ", ")
  else
    list_of_children = "nobody"
  say father "begat" list_of_children
end

/* ----------------------------- */

key = "f1.txt" ; files.key = TRUE ; key = "f2.txt" ; files.key = TRUE

do while regStemDoOver('files.', 'file')
  call STREAM file, 'C', 'OPEN READ'

  do while LINES(file) > 0
    /* 'match' is a REXXToolkit custom function [see Appendix] */
    if match(LINEIN(file), "#include") then ; includes.file = TRUE
  end

  call STREAM file, 'C', 'CLOSE'
end

/* ----------- */

do while regStemDoOver('files.', 'file')
  if SYMBOL('includes.file') \= 'VAR' then ; includes_free.file = TRUE
end

Program: dutree

/* ------------------------------------------------------------------ */
/* Program: dutree                                                    */
/* ------------------------------------------------------------------ */

@@INCOMPLETE@@
@@INCOMPLETE@@