diff --git a/README.md b/README.md
index bd85c83..6df0b91 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ The **macroArray** package implements an array, a hash table, and a dictionary c
);
```
-SHA256 digest for the latest version of `macroArray`: F*6A22A01868F4203862B3685F543D723C7DB8E9AB3C1A6357D2BFA030971B0D3C
+SHA256 digest for the latest version of `macroArray`: F*E9C0C58FB36AC40C76A518066B8C6F9942202A9DB2C2D737E95D2BB6E4ECED50
[**Documentation for macroArray**](./macroarray.md "Documentation for macroArray")
diff --git a/hist/1.1.1/macroarray.md b/hist/1.1.1/macroarray.md
new file mode 100644
index 0000000..ca8b22d
--- /dev/null
+++ b/hist/1.1.1/macroarray.md
@@ -0,0 +1,2204 @@
+- [The macroArray package](#macroarray)
+- [Content description](#content-description)
+ * [`%appendArray()` macro](#appendarray-macro)
+ * [`%appendCell()` macro](#appendcell-macro)
+ * [`%array()` macro](#array-macro)
+ * [`%concatArrays()` macro](#concatarrays-macro)
+ * [`%deleteMacArray()` macro](#deletemacarray-macro)
+ * [`%do_over()` macro](#do-over-macro)
+ * [`%do_over2()` macro](#do-over2-macro)
+ * [`%do_over3()` macro](#do-over3-macro)
+ * [`%make_do_over()` macro](#make-do-over-macro)
+ * [`%mcHashTable()` macro](#mchashtable-macro)
+ * [`%mcDictionary()` macro](#mcdictionary-macro)
+ * [`%QzipArrays()` macro](#qziparrays-macro)
+ * [`%zipArrays()` macro](#ziparrays-macro)
+ * [`%sortMacroArray()` macro](#sortmacroarray-macro)
+
+ * [License](#license)
+
+---
+
+# The macroArray package [ver. 1.1.1] ###############################################
+
+The **macroArray** package implements a macro array facility:
+- `%array()`,
+- `%do_over()`,
+- `%make_do_over()`,
+- `%deletemacarray()`,
+- `%concatarrays()`,
+- `%appendcell()`,
+- `%mcHashTable()`,
+- `%zipArrays()`,
+- `%sortMacroArray()`,
+- `%mcDictionary()`,
+- etc.
+
+The set of macros, which emulates classic
+data-step-array functionality on the macro
+programming level, is provided.
+
+*Note:*
+If you are working with BIG macroarrays do not
+forget to verify your session setting for macro
+memory limits. Run:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ proc options group = macro;
+ run;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+to verify the following options:
+
+| option | description |
+|-------------:|:-----------------------------------------------------------------------------------------------|
+|`MEXECSIZE=` | specifies the maximum macro size that can be executed in memory. |
+|`MSYMTABMAX=` | specifies the maximum amount of memory available to the macro variable symbol table or tables. |
+|`MVARSIZE=` | specifies the maximum size for a macro variable that is stored in memory. |
+
+---
+
+Package contains:
+ 1. macro appendarray
+ 2. macro appendcell
+ 3. macro array
+ 4. macro concatarrays
+ 5. macro deletemacarray
+ 6. macro do_over
+ 7. macro do_over2
+ 8. macro do_over3
+ 9. macro make_do_over
+ 10. macro mcdictionary
+ 11. macro mchashtable
+ 12. macro qziparrays
+ 13. macro sortmacroarray
+ 14. macro ziparrays
+
+Required SAS Components:
+ *Base SAS Software*
+
+*SAS package generated by generatePackage, version 20230904*
+
+The SHA256 hash digest for package macroArray:
+`F*E9C0C58FB36AC40C76A518066B8C6F9942202A9DB2C2D737E95D2BB6E4ECED50`
+
+---
+# Content description ############################################################################################
+
+## >>> `%appendArray()` macro: <<< ############
+
+The `%appendArray()` macro is a macrowrapper
+which allows to concatenate two macroarrays
+created by `%array()` macro.
+
+By default values of the second macroarray are *not* removed.
+
+Dimensions of the first macroarray are extended.
+
+The `%appendArray()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%appendArray(
+ first
+ ,second
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `first` - *Required*, a name of a macroarray created by the `%array()` macro.
+
+2. `second` - *Required*, a name of a macroarray created by the `%array()` macro.
+
+
+
+
+### EXAMPLES AND USECASES: ######################################################
+
+**EXAMPLE 1.** Append macroarrays LL and MM.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ll[2:4] $ 12,
+ function = quote(put(today() + 10*_I_, yymmdd10.)),
+ macarray=Y
+ )
+ %array(mm[10:13] $ 1200,
+ function = quote(repeat("A",12*_I_)),
+ macarray=Y
+ )
+ %put *%ll(2)*%ll(3)*%ll(4)*;
+
+ %appendArray(ll, mm);
+ %put *%ll(2)*%ll(3)*%ll(4)*%ll(5)*%ll(6)**%ll(7)*%ll(8)*;
+
+ %put *%mm(10)**%mm(11)*%mm(12)*%mm(13)*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Error handling.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %appendArray(ll, )
+ %appendArray(, mm)
+
+ %appendArray(noExistA, noExistB)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%appendCell()` macro: <<< ##############
+
+The `%appendCell()` macro allows to append
+a macrovariable to a macroarray created by the `%array()` macro.
+
+Dimensions of the macroarray are extended.
+
+The `%appendCell()` macro executes like a pure macro code.
+
+### SYNTAX: ####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%appendCell(
+ first
+ ,second
+ ,hilo
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `first` - *Required*, a name of a macroarray created by the `%array()` macro.
+
+2. `second` - *Required*, a name of a macrovariable to be append to the macroarray.
+
+3. `hilo` - *Required*, if `H` macrovariable is appended at the end
+ if `L` macrovariable is appended at the beginning
+);
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Create two macro wrappers.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %* Macro wrapper to append a macrovariable to the end of a macroarray;
+ %macro appendHC(array,cell);
+ %appendCell(&array.,&cell.,H)
+ %mend appendHC;
+
+ %* macro wrapper to append a macrovariable to the beginning of a macroarray;
+ %macro appendLC(array,cell);
+ %appendCell(&array.,&cell.,L)
+ %mend appendLC;
+
+
+ %* create macroarrays X and variables W,Y,Z;
+
+ %array(X[2:4] $ ("AAA", "BBB", "CCC"), macarray=Y)
+ %let W=1;
+ %let Y=2;
+ %let Z=3;
+ %put *%do_over(X)*&=W*&=Y*&=Z*;
+
+ %put BEFORE *%do_over(X)**&=xLBOUND*&=xHBOUND*&=xN*;
+ %appendCell(X,Y,H)
+ %put AFTER1 *%do_over(X)**&=xLBOUND*&=xHBOUND*&=xN*;
+
+ %appendLC(X,W)
+ %put AFTER2 *%do_over(X)**&=xLBOUND*&=xHBOUND*&=xN*;
+
+ %appendHC(X,Z)
+ %put AFTER3 *%do_over(X)**&=xLBOUND*&=xHBOUND*&=xN*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Error handling
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %appendCell(X,Y,blahblah)
+
+ %appendCell(X,,H)
+ %appendCell(,Y,H)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**EXAMPLE 3.** Adding variable below lower bound.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(zero[0:2] $ ("AAA", "BBB", "CCC"), macarray=Y)
+ %let belowzero=zzz;
+
+ %put BEFORE *%do_over(zero)**&=zeroLBOUND*&=zeroHBOUND*&=zeroN*;
+ %appendCell(zero,belowzero,L)
+ %put AFTER *%do_over(zero)**&=zeroLBOUND*&=zeroHBOUND*&=zeroN*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%array()` macro: <<< #######################
+
+The code of a macro was inspired by
+*Ted Clay's* and *David Katz's* macro `%array()`.
+
+The `%array()` macro version provided in the package
+is designed to facilitate
+the idea of macro array concept, i.e. *a list of
+macrovariables with common prefix and numerical suffixes*.
+Usually such construction is then resolved by
+double ampersand syntax, e.g. `&&perfix&i` or similar one.
+
+What is new/extension to the `%array()` macro concept are:
+
+0. The syntax is closer to the data step one.
+1. It is a pure macro code (it can be executed in any place
+ of 4GL code), this includes generating macro arrays out
+ of datasets.
+2. When a macroarrray is created it allows also to generate
+ a new macro (named the same as the array name) and replace
+ the double ampersand syntax with more array looking one,
+ i.e. for array ABC user can have `%ABC(1)`, `%ABC(2)`, or `%ABC(&i)`
+ constructions.
+3. The array macro allows to use data step functions to generate
+ array's entries.
+
+The `%array()` macro executes like a pure macro code.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(
+ array
+ <,function=>
+ <,before=>
+ <,after=>
+ <,vnames=N>
+ <,macarray=N>
+ <,ds=>
+ <,vars=>
+ <,q=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `array` - *Required*, an array name and a declaration/definition of an array,
+ e.g. `myArr[*] x1-x3 (4:6)`
+ or `myBrr[*] $ y1-y3 ("a" "b" "c")`
+ or `myCrr[3] $ ("d d d" "e,e,e" "f;f;f")`
+ or `myDrr p q r s`.
+ Macrovariables created by the macro are *global*.
+ If an array name is `_` (single underscore) then attached variables
+ list names are used, a call of the form:
+ `%array(_[*] p1 q2 r3 s4 (-1 -2 -3 -4))`
+ will create macrovariables: `p1`, `q2`, `r3`, and `s4` with respective
+ values: `-1`, `-2`, `-3`, and `-4`.
+ Three additional *global* macrovariables:
+ `LBOUND`, `HBOUND`, and `N`
+ are generated with the macroarray. See examples for more use-cases.
+
+* `function=` - *Optional*, a function or an expression to be applied to all array cells,
+ `_I_` is as array iterator, e.g. `_I_ + rand("uniform")`.
+
+* `before=` - *Optional*, a function or an expression to be added before looping through
+ array, e.g. `call streaminit(123)`.
+
+* `after=` - *Optional*, a function or an expression to be added after looping through
+ array, e.g. `call sortn(ABC)`.
+
+* `vnames=N` - *Optional*, default value `N`, if set to `Y`/`YES` then macroarray is built based
+ on variables names instead values, e.g.
+ `%array(myArr[*] x1-x3 (4:6), vnames=Y)`
+ will use `x1`, `x2`, and `x3` as values instead `4`, `5`, and `6`.
+
+* `macarray=N` - *Optional*, default value `N`, if set to `Y`/`YES` then a macro, named with the array
+ name, is compiled to create convenient envelope for multiple ampersands, e.g.
+ `%array(myArr[*] x1-x3 (4:6), macarray=Y)`
+ will create `%myArr(J)` macro which will allow to extract "data"
+ from macroarray like:
+ `%let x = %myArr(1);`
+ or when used with second parameter equal `I` (insert) allow to overwrite macroarrays
+ value:
+ `%let %myArr(17,i) = 42;`
+ If set to `M` then for a given array name the macro symbols table is scanned for
+ macrovariables with prefix like the array name and numeric suffixes,
+ then the minimum and the maximum index is determined
+ and all not existing global macrovariables are created and
+ a macro is generated in the same way as for the `Y` value.
+
+* `ds=` - *Optional*, use a dataset as a basis for a macroarray data,
+ if used by default overwrites use of the `array` parameter, honors `macarray=`
+ argument, dataset options are allowed, e.g. `sashelp.class(obs=5)`
+
+* `vars=` - *Optional*, a list of variables used to create macroarrays from a dataset,
+ the list format can be as follows (`<...>` means optional):
+ `variable1 <... variableN>`
+ delimiters are hash(`#`) and pipe(`|`), currently only space
+ is supported as separator, the meaning of `#` and `|` delimiters
+ will be explained in the following example:
+ if the `vars = height#h weight weight|w age|` value is provided
+ then the following macroarrays will be created:
+ 1) macroarray "H" with ALL(`#`) values of variable "height"
+ 2) macroarray "WEIGHT" with ALL(no separator is equivalent to #)
+ values of variable "weight"
+ 3) macroarray "W" with UNIQUE(|) values of variable "weight" and
+ 4) macroarray "AGE" with UNIQUE(|) values of variable "age".
+
+* `q=` - *Optional*, indicates (when set to `1`) if the value be surrounded by quotes.
+ It uses `quote(cats(...))` combo under the hood. Default value is `0`.
+ Ignored for `macarray=M`.
+
+
+---
+
+### EXAMPLES AND USECASES: ####################################################
+
+
+**EXAMPLE 1.** Basic use-case.
+ Creating macroarray like in the array statement.
+ Values not variables names are used by default.
+ Different types of brackets are allowed.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(a[*] x1-x5 (1:5))
+
+ %array(b{5} (5*17), q=1)
+
+ %* Mind the $ since it is a character array!;
+ %array(c(3) $ 10 ("a A" "b,B" "c;C"))
+
+ %array(d x1-x5 (5 4 3 2 1))
+ %put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Index ranges.
+ If range starts < 0 then it is shifted to 0.
+ In case when range is from `1` to `M`
+ then macrovariable `N` is set to `M`
+ In case when range is different
+ the `N` returns number of
+ elements in the array `(Hbound - Lbound + 1)`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(d[-2:2] $ ("a" "b" "c" "d" "e"))
+ %put &=dLBOUND. &=dHBOUND. &=dN.;
+ %put &=d0. &=d1. &=d2. &=d3. &=d4.;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Functions.
+ It is possible to assign value of a function
+ or an expression to a cell of the array,
+ e.g. `array[_I_] = function(...)`.
+ You can use an iterator in a function.
+ As in case of usual arrays it is `_I_`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(e[-3:3] $, function = "A" )
+ %put &=eLBOUND. &=eHBOUND. &=eN.;
+ %put &=e0. &=e1. &=e2. &=e3. &=e4. &=e5. &=e6.;
+
+ %array(f[-3:3], function = (2**_I_) )
+ %put &=fLBOUND. &=fHBOUND. &=fN.;
+ %put &=f0. &=f1. &=f2. &=f3. &=f4. &=f5. &=f6.;
+
+ %array(g[0:2], function = ranuni(123) )
+ %put &=gLBOUND. &=gHBOUND. &=gN.;
+ %put &=g0. &=g1. &=g2.;
+
+ %* Or something more complex;
+ %array(gg[0:11] $ 11, function = put(intnx("MONTH", '1jun2018'd, _I_, "E"), yymmn.), q=1)
+ %put &=ggLBOUND. &=ggHBOUND. &=ggN.;
+ %put &=gg0 &=gg1 &=gg2 ... &=gg11;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** Functions cont.
+ If there is need for set-up something *before* or *after*:
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(h[10:12]
+ ,function = rand('Uniform')
+ ,before = call streaminit(123)
+ ,after = call sortn(of h[*])
+ )
+ %put &=h10. &=h11. &=h12.;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 5.** Fibonacci series.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(i[1:10] (10*0)
+ ,function = ifn(_I_ < 2, 1, sum(i[max(_I_-2,1)], i[max(_I_-1,2)]) ) )
+ %put &=i1 &=i2 &=i3 &=i4 &=i5 &=i6 &=i7 &=i8 &=i9 &=i10;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 6a.** Quoted "Uppercas Letters"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(UL[26] $, function = byte(rank("A")+_I_-1) , q=1)
+ %put &=UL1 &=UL2 ... &=UL25 &=UL26;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 6b.** "Lowercase Letters"
+ Extended by `macarray=Y` option and
+ the input mode support (with `I`).
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ll[26] $, function = byte(rank("a")+_I_-1), macarray=Y)
+ %put *%ll(&llLBOUND.)*%ll(3)*%ll(4)*%ll(5)*...*%ll(25)*%ll(&llHBOUND.)*;
+
+ %* The range handling, warning;
+ %put *%ll(265)*;
+
+ %* The input mode;
+ %put *before:*%ll(2)*;
+ %let %ll(2,I) = bbbbb;
+ %put *after: *%ll(2)*;
+
+ %* The range handling, error;
+ %let %ll(265,I) = bbb;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 7.** The use of `vnames=Y`
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(R R1978-R1982)
+ %put &=R1 &=R2 &=R3 &=R4 &=R5;
+
+ %array(R R1978-R1982 (78:82))
+ %put &=R1 &=R2 &=R3 &=R4 &=R5;
+
+ %array(R R1978-R1982 (78:82), vnames=Y)
+ %put &=R1 &=R2 &=R3 &=R4 &=R5;
+
+ %array(R R1978-R1982, vnames=Y)
+ %put &=R1 &=R2 &=R3 &=R4 &=R5;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 8.** A "no name" array i.e. the `_[*]` array
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(_[*] x1-x5 (1:5))
+ %put _user_;
+
+ %array(_[*] p q r s (4*42))
+ %put _user_;
+
+ %* If no variables names than use _1 _2 ... _N;
+ %array(_[4] (-1 -2 -3 -4))
+ %put &=_1 &=_2 &=_3 &=_4;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 9.** Pure macro code can be used in a data step.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ data test1;
+ set sashelp.class;
+ %array(ds[*] d1-d4 (4*17))
+ a1 = &ds1.;
+ a2 = &ds2.;
+ a3 = &ds3.;
+ a4 = &ds4.;
+ run;
+
+ data test2;
+ set sashelp.class;
+ %array(_[*] j k l m (4*17))
+ a1 = &j.;
+ a2 = &k.;
+ a3 = &l.;
+ a4 = &m.;
+ run;
+
+ data test3;
+ set sashelp.class;
+ %array(alpha[*] j k l m (101 102 103 104), macarray=Y)
+ a1 = %alpha(1);
+ a2 = %alpha(2);
+ a3 = %alpha(3);
+ a4 = %alpha(4);
+ a5 = %alpha(555);
+ run;
+
+ data test4;
+ set sashelp.class;
+ %array(beta[*] j k l m (101 102 103 104), vnames=Y, macarray=Y)
+ a1 = "%beta(1)";
+ a2 = "%beta(2)";
+ a3 = "%beta(3)";
+ a4 = "%beta(4)";
+ a5 = "%beta(555)";
+ run;
+
+ data test5;
+ set sashelp.class;
+ %array(gamma[4] $ 12 ("101" "102" "103" "104"), macarray=Y)
+ a1 = "%gamma(1)";
+ a2 = "%gamma(2)";
+ a3 = "%gamma(3)";
+ a4 = "%gamma(4)";
+ a5 = "%gamma(555)";
+ run;
+
+ data test6;
+ set sashelp.class;
+ %array(ds = sashelp.cars, vars = Cylinders|, macarray=Y)
+ a0 = %Cylinders(0);
+ a1 = %Cylinders(1);
+ a2 = %Cylinders(2);
+ a3 = %Cylinders(3);
+ a4 = %Cylinders(4);
+ a5 = %Cylinders(555);
+ run;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 10.** Creating an array from a dataset, basic case.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ds = sashelp.class, vars = height weight age)
+ %put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 11. Creating an array from a dataset, advanced.
+ If: `vars = height#h weight weight|w age|`
+ then create:
+ 1. macroarray "h" with ALL(#) values of variable "height"
+ 2. macroarray "weight" with ALL(no separator is equivalent to #) values of variable "weight"
+ 3. macroarray "w" with UNIQUE(|) values of variable "weight"
+ 4. macroarray "age" with UNIQUE(|) values of variable "age"
+ Currently the only separator in VARS is a space.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ds = sashelp.class, vars = height#h weight weight|w age|, q=1)
+ %put _user_;
+
+ %array(ds = sashelp.class, vars = height#hght weight weight|wght age|, macarray=Y, q=1)
+ %put *%hght(&hghtLBOUND.)**%weight(2)**%wght(&wghtHBOUND.)**%age(3)*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 12.** Creating an array from a dataset with dataset options
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ds = sashelp.cars(obs=100 where=(Cylinders=6)), vars = Make| Type| Model, macarray=Y)
+ %put *%make(&makeLBOUND.)*%Model(2)*%Model(3)*%Model(4)*%type(&typeHBOUND.)*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 13.** Creating an array and macro from existing list of macrovariables
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %let myTest3 = 13;
+ %let myTest6 = 16;
+ %let myTest9 = 19;
+
+ %array(myTest, macarray=M, q=1)
+ %do_over(myTest, phrase = %nrstr(%put *&_I_.*%myTest(&_I_.)*;))
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---
+
+
+
+## >>> `%concatArrays()` macro: <<< ###########
+
+The `%concatArrays()` macro allows to concatenate
+two macroarrays created by the `%array()` macro.
+
+By default values of the second macroarray are removed.
+
+Dimensions of the first macroarray are extended.
+
+The `%concatArrays()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%concatArrays(
+ first
+ ,second
+ <,removeSecond=Y>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `first` - *Required*, a name of a macroarray created by the `%array()` macro.
+
+2. `second` - *Required*, a name of a macroarray created by the `%array()` macro.
+
+* `removeSecond=Y` - *Optional*, default value `Y`, if set to `Y` then
+ the second array is removed.
+
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Concatenate macroarrays LL and MM.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(ll[2:4] $ 12,
+ function = quote(put(today() + 10*_I_, yymmdd10.)),
+ macarray=Y
+ )
+ %array(mm[10:13] $ 12000,
+ function = quote(repeat("A",123*_I_)),
+ macarray=Y
+ )
+ %put *%ll(2)*%ll(3)*%ll(4)*;
+
+ %concatArrays(ll, mm);
+ %put *%ll(2)*%ll(3)*%ll(4)*%ll(5)*%ll(6)**%ll(7)*%ll(8)*;
+
+ %put *%mm(10)**%mm(11)*%mm(12)*%mm(13)*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Error handling.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %concatArrays(ll, )
+ %concatArrays(, mm)
+
+ %concatArrays(noExistA, noExistB)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%deleteMacArray()` macro: <<< #######
+
+The `%deleteMacArray()` macro allows to delete
+macroarrays created by the `%array()` macro.
+
+The `%deleteMacArray()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%deleteMacArray(
+ arrs
+ <,macarray=N>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `arrs` - *Required*, a space separated list of manes
+ of macroarray created by the `%array()` macro.
+
+* `macarray=N` - *Optional*, indicator should a macro
+ associated with macroarray to be deleted?
+ If `Y` or `YES` then the associated macro is deleted.
+
+
+
+
+## >>> `%do_over()` macro: <<< ######################
+
+The code of the macro was inspired by
+*Ted Clay's* and *David Katz's* macro `%do_over()`.
+
+The `%DO_OVER()` macro allows to iterate over macroarray created with
+the `macarray=Y` parameter of the `%ARRAY()` macro.
+
+The `%do_over()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%do_over(
+ array
+ <,phrase=%nrstr(%&array(&_I_.))>
+ <,between=%str( )>
+ <,which = >
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `array` - *Required*, indicates a macroarray which metadata (Lbound, Hbouns)
+ are to be used to loop in the `%do_over()`
+
+* `phrase=` - *Optional*, Default value `%nrstr(%&array(&_I_.))`,
+ a statement to be called in each iteration
+ of the internal do_over's loop. Loop iterator is `_I_`,
+ if you want to use `_I_` or array name
+ [e.g. `%myArr(&_I_.)`] *enclose it* in the `%NRSTR()`
+ macro quoting function.
+
+* `between=` - *Optional*, default value `%str( )` (space),
+ a statement to be called in between each
+ iteration of the internal do_over loop.
+ If macroquoted (e.g. `%str( + )`) then the `%unquote()`
+ function is automatically applied.
+
+* `which=` - *Optional*, a _SPACE_ separated list of indexes which
+ should be used to iterate over selected macroarray.
+ Possible special characters are `H` and `L` which means
+ *high* and *low* bound of an array, list could be set with
+ colons(`:`) in form of `start:end:by` (*no spaces between!*),
+ if `by` is omitted the default is `1`. If possible use
+ `1:5` rather `1 2 3 4 5` since the firs works faster.
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Simple looping.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(beta[*] j k l m (101 102 103 104), vnames=Y, macarray=Y)
+
+ %put #%do_over(beta)#;
+
+ %put #%do_over(beta, phrase=%nrstr("%beta(&_I_.)"), between=%str(,))#;
+
+ data test1;
+ %array(beta[*] j k l m (101 102 103 104), vnames=Y, macarray=Y)
+ %do_over(beta, phrase=%nrstr(a&_I_. = "%beta(&_I_.)";))
+ put _all_;
+ run;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Multiple arrays looping.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(alpha[*] j k l m n, vnames=Y, macarray=Y)
+ %array( beta[5] $ , function = "a", macarray=Y)
+ %array(gamma[4] (101 102 103 104), macarray=Y)
+
+ data test2;
+ call streaminit(123);
+ %do_over(beta
+ , phrase = %nrstr(%beta(&_I_.) = %gamma(&_I_.) * rand('Uniform'); output;)
+ , between = put _all_;
+ );
+ put _all_;
+ run;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Multiple arrays looping, cont.
+ Create multiple datasets.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %do_over(beta
+ , phrase = %nrstr(
+ data %alpha(&_I_.)2;
+ call streaminit(123);
+ %beta(&_I_.)x = %gamma(&_I_.) * rand('Uniform');
+ output;
+ run;
+ )
+ )
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** Multiple arrays looping, cont.
+ Create multiple datasets using a macro.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %macro doit(ds, var=a, val=1);
+ data &ds.;
+ call streaminit(123);
+ &var. = &val. * rand('Uniform');
+ output;
+ run;
+ %mend doit;
+
+ %do_over(beta
+ , phrase = %nrstr(
+ %DOIT(%alpha(&_I_.)1, var = %beta(&_I_.), val = %gamma(&_I_.))
+ )
+ )
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 5.** `%do_over()` inside `%array()`
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(test[*] x1-x12 (1:12), macarray=Y)
+
+ %put **%test(1)**%test(12)**;
+
+ %put #%do_over(test)#;
+
+ %array(abc[*] x1-x12 (%do_over(test,phrase=%nrstr(%eval(100-%test(&_I_.))))), macarray=Y)
+
+ %put **%abc(1)**%abc(12)**;
+
+ %put #%do_over(abc)#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 6.** Looping over array with *macroquoted* separator.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(alpha[11] (5:15), macarray=Y)
+
+ %let x = %do_over(alpha
+ , phrase = %NRSTR(%alpha(&_I_.))
+ , between= %str( + )
+ );
+ %put &=x.;
+ %put %sysevalf(&x.);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 7.** Working with the `WHICH=` optional parameter
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(test[*] x01-x12, vnames= Y, macarray=Y)
+
+ %put #%do_over(test)#;
+
+ %put #%do_over(test, which= 1 3 5)#;
+
+ %put #%do_over(test, which= 1:5)#;
+
+ %put #%do_over(test, which= 1:5:2 7 8)#;
+
+ %put #%do_over(test, which= L:H l:h)#;
+
+ %put #%do_over(test, which= L:3 10:h)#;
+
+ %put #%do_over(test, which= L:H h:l:-1 13 14)#;
+
+ %put #%do_over(test, which= %eval(1+1):%eval(5+1))#;
+
+ %put #%do_over(test, which= L:H h:l:-1 13 14, between=%str(,))#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%do_over2()` macro: <<< ####################
+
+The code of the macro was inspired by
+*Ted Clay's* and *David Katz's* macro `%do_over()`.
+
+The `%DO_OVER2()` macro allows to iterate over *two* macroarray created with
+the `macarray=Y` parameter of the `%ARRAY()` macro.
+
+The `%do_over2()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%do_over2(
+ arrayI
+ ,arrayJ
+ <,phrase=%nrstr(%&arrayI(&_I_.) %&arrayJ(&_J_.))>
+ <,between=%str( )>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `arrayI` - Required, indicates the first macroarray which metadata (Lbound, Hbouns)
+ are to be used in the outer loop in the `%do_over2()`
+
+2. `arrayJ` - Required, indicates the second macroarray which metadata (Lbound, Hbouns)
+ are to be used in the inner loop in the `%do_over2()`
+
+* `phrase=` - *Optional*, default value `%nrstr(%&arrayI(&_I_.) %&arrayJ(&_J_.))`,
+ a statement to be called in each iteration
+ of the *inner* loop. The outer loop iterator is `_I_`,
+ the inner loop iterator is `_J_`,
+ if you want to use `_I_`, `_J_`, or arrays names
+ [e.g. `%myArr(&_I_.)`] *enclose them* in the `%NRSTR()`
+ macro quoting function.
+
+* `between=` - *Optional*, default value `%str( )` (space),
+ a statement to be called in between each
+ iteration of the internal do_over2 loop.
+ If macroquoted (e.g. `%str( + )`) then the `%unquote()`
+ function is automatically applied.
+
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Looping over two arrays.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(alpha[*] j k l m n, vnames=Y, macarray=Y)
+ %array( beta[4] (101 102 103 104), macarray=Y)
+
+ %put *%do_over2(alpha, beta
+ , phrase = %NRSTR((%alpha(&_I_.), %beta(&_J_)))
+ )*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Looping over two arrays with a separator.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(alpha[11] (5:15), macarray=Y)
+ %array( beta[ 4] (101 102 103 104), macarray=Y)
+
+ %let x = %do_over2(alpha, beta
+ , phrase = %NRSTR((%alpha(&_I_.) * %beta(&_J_)))
+ , between= +
+ );
+ %put &=x.;
+ %put %sysevalf(&x.);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Looping over two arrays with *macroquoted* separator.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(alpha[11] (5:15), macarray=Y)
+ %array( beta[ 4] (101 102 103 104), macarray=Y)
+
+ %let x = %do_over2(alpha, beta
+ , phrase = %NRSTR((%alpha(&_I_.) * %beta(&_J_)))
+ , between= %str( + )
+ );
+ %put &=x.;
+ %put %sysevalf(&x.);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%do_over3()` macro: <<< ####################
+
+The code of the macro was inspired by
+*Ted Clay's* and *David Katz's* macro `%do_over()`.
+
+The `%DO_OVER3()` macro allows to iterate over *three* macroarray created with
+the `macarray=Y` parameter of the `%ARRAY()` macro.
+
+The `%do_over3()` macro executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%do_over2(
+ arrayI
+ ,arrayJ
+ ,arrayK
+ <,phrase=%nrstr(%&arrayI(&_I_.) %&arrayJ(&_J_.) %&arrayK(&_K_.))>
+ <,between=%str( )>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `arrayI` - *Required*, indicates the first macroarray which metadata (Lbound, Hbouns)
+ are to be used in the outer loop in the `%do_over3()`
+
+2. `arrayJ` - *Required*, indicates the second macroarray which metadata (Lbound, Hbouns)
+ are to be used in the inner loop in the `%do_over3()`
+
+3. `arrayK` - *Required*, indicates the third macroarray which metadata (Lbound, Hbouns)
+ are to be used in the inner loop in the `%do_over3()`
+
+* `phrase=` - *Optional*, default value `%nrstr(%&arrayI(&_I_.) %&arrayJ(&_J_.) %&arrayK(&_K_.))`,
+ a statement to be called in each iteration
+ of the *inner* loop. The *outer* loop iterator is `_I_`,
+ the *middle* loop iterator is `_J_`, the *inner* loop iterator is `_K_`,
+ if you want to use `_I_`, `_J_`, `_K_`, or arrays names
+ [e.g. `%myArr(&_I_.)`] *enclose them* in the `%NRSTR()`
+ macro quoting function.
+
+* `between=` - *Optional*, default value `%str( )` (space),
+ a statement to be called in between each
+ iteration of the internal do_over2 loop.
+ If macroquoted (e.g. `%str( + )`) then the `%unquote()`
+ function is automatically applied.
+
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Looping over 3 macroarrays.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(a1_[2] (0 1), macarray=Y)
+ %array(a2_[2] (2 3), macarray=Y)
+ %array(a3_[2] (4 5), macarray=Y)
+
+ %do_over3(a1_, a2_, a3_
+ , phrase = %NRSTR(%put (%a1_(&_I_.), %a2_(&_J_), %a3_(&_K_));)
+ )
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**EXAMPLE 2.** Looping 3 times over a macroarray.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(a[0:2] (0 1 2), macarray=Y)
+
+ %do_over3(a, a, a
+ , phrase = %NRSTR(%put (%a(&_I_.), %a(&_J_), %a(&_K_));)
+ )
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+
+
+## >>> `%make_do_over()` macro: <<< ###########
+
+The code of the macro was inspired by
+*Ted Clay's* and *David Katz's* macro `%do_over()`.
+
+The `%make_do_over()` macro allows to generate
+the `%DO_OVER()` macros. It works *only* for *n>3*!
+
+The `%make_do_over()` macro does *not* executes like a pure macro code.
+
+### SYNTAX: #####################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%make_do_over(
+ size
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `size` - *Required*, indicates the number of dimensions
+ (i.e. inner loops) of the `%DO_OVER()` macro.
+
+
+
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Code of created "4-loop" `%DO_OVER4()` macro
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %macro do_over4(
+ arrayI1,
+ arrayI2,
+ arrayI3,
+ arrayI4,
+ phrase=%nrstr(
+ %&arrayI1(&_I1_.)
+ %&arrayI2(&_I2_.)
+ %&arrayI3(&_I3_.)
+ %&arrayI3(&_I4_.)
+ ),
+ between=%str( )
+ );
+ %local _I1_ _I2_ _I3_ _I4_;
+ %do _I1_ = &&&arrayI1.LBOUND %to &&&arrayI1.HBOUND;
+ %do _I2_ = &&&arrayI2.LBOUND %to &&&arrayI2.HBOUND;
+ %do _I3_ = &&&arrayI3.LBOUND %to &&&arrayI3.HBOUND;
+ %do _I4_ = &&&arrayI4.LBOUND %to &&&arrayI4.HBOUND;
+ %if not (
+ &_I1_. = &&&arrayI1.LBOUND
+ AND &_I2_. = &&&arrayI2.LBOUND
+ AND &_I3_. = &&&arrayI3.LBOUND
+ AND &_I4_. = &&&arrayI4.LBOUND
+ )
+ %then %do;%unquote(&between.)%end;%unquote(%unquote(&phrase.))
+ %end;
+ %end;
+ %end;
+ %end;
+ %mend do_over4;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Create a "4-loop" `%DO_OVER4()` macro
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %make_do_over(4);
+
+ %array(a1_[2] (0 1), macarray=Y)
+
+ %do_over4(a1_, a1_, a1_, a1_
+ , phrase = %NRSTR(%put (%a1_(&_I1_.), %a1_(&_I2_), %a1_(&_I3_), %a1_(&_I4_));)
+ )
+
+ %put *%do_over4(a1_, a1_, a1_, a1_
+ , between = *
+ )*;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**EXAMPLE 3.** Create a "5-loop" `%DO_OVER5()` macro
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %make_do_over(5);
+
+ %array(a1_[2] (0 1), macarray=Y)
+
+ %do_over5(a1_, a1_, a1_, a1_, a1_
+ , phrase = %NRSTR(%put (%a1_(&_I1_.), %a1_(&_I2_), %a1_(&_I3_), %a1_(&_I4_), %a1_(&_I5_));)
+ )
+
+ %put *%do_over5(a1_, a1_, a1_, a1_, a1_
+ , between = *
+ )*
+ ;
+
+ options nomprint;
+ data test2;
+ %do_over5(a1_, a1_, a1_, a1_, a1_
+ , phrase = %NRSTR(x1 = %a1_(&_I1_.); x2 = %a1_(&_I2_); x3 = %a1_(&_I3_); x4 = %a1_(&_I4_); x5 = %a1_(&_I5_);)
+ , between = output;
+ )
+ output;
+ run;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** Create all from 6 to 10 "do_overs"
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+ %array(loop[6:10] (6:10), macarray=Y)
+ %do_over(loop
+ , phrase = %nrstr(
+ %make_do_over(%loop(&_I_.))
+ )
+ );
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---
+
+## >>> `%mcHashTable()` macro: <<< #######################
+
+The `%mcHashTable()` macro provided in the package
+is designed to facilitate the idea of a "macro hash table"
+concept, i.e. *a list of macrovariables with common prefix
+and suffixes generated as a hash digest* which allows
+to use values other than integers as indexes.
+
+The `%mcHashTable()` macro allows to generate other macros
+which behaves like hash tables or dictionaries. See examples below.
+
+The `%mcHashTable()` macro executes like a pure macro code.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(
+ H
+ <,METHOD>
+ <,HASH=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `H` - *Required*, a hash table macro name and a declaration/definition,
+ e.g. `mcHashTable(HT)`. It names a macro which is generated by
+ the `%mcHashTable()` macro. Provided name cannot be empty
+ or an underscore (`_`). No longer than *16* characters.
+
+2. `METHOD` - *Optional*, if empty (or DECLARE or DCL) then the code of
+ a macro hash table is compiled.
+ If `DELETE` then the macro hash table named by `H` and all
+ macrovariables named like "`&H._`" are deleted.
+
+* `HASH=` - *Optional*, indicates which hashing algorithms should be used,
+ available values are `CRC32` or `MD5`, the `CRC32` is the default.
+
+---
+
+### THE CREATED MACRO `%&H.()`: ####################################################
+
+The created macro imitates behaviour of a hash table or a dictionary.
+It is *not* dedicated for "long-ish" lists (above 1000 elements) since
+the performance may be poor.
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%&H.(
+ METHOD
+ <,KEY=>
+ <,DATA=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `METHOD` - *Required*, indicate what behaviour should be executed.
+ Allowed values are:
+ - `ADD`, adds key and data portion to the macro hash table,
+ *multiple data portions* are available for one key.
+ - `FIND`, tests if given key exists in the macro hash table
+ and, if yes, returns data value associated with the key.
+ For multiple data portions see the `data=` parameter.
+ - `DP` (data portion) or `CHECK`, returns the number of data
+ portions for a given key.
+ - `CLEAR` removes all data and keys values.
+ - `KEYIDX`, allows to get data by the key index rather than value.
+ - `KEYVAL`, returns key value for a given key index.
+ - `CHECKIDX`, returns the number of data portions for
+ a given key index.
+
+* `KEY=` - *Optional*, provides key value for `ADD`, `FIND`,`DP`, `CHECK`
+ `CHECKIDX`, `KEYIDX`, and `KEYVAL` methods. Leading and trimming
+ spaces are removed from the value.
+ The `hashing(CRC32,...)` function or the `MD5(...)` function is
+ used to generate the hash.
+
+* `DATA=` - *Optional*, provides data value for the `ADD` method and
+ for the`FIND` method provides data portion number to be
+ extracted. Default value is `1` (used by the `FIND` method).
+
+
+When macro is executed and when data are added the following types of
+*global* macrovariables are created:
+- `&H._########`,
+- `&H._########_Xk`,
+- `&H._########_Xi`,
+- `&H._########_Xi_j`,
+- `&H._KEYNUM`,
+- and `&H._KEY_i`.
+
+The `#` represents value generated by the `hashing(CRC32,...)` function
+or the `MD5(...)` function for the given key.
+
+The first type keeps information about possible collision for the key.
+
+The second type keeps information about value of a given key,
+the `X` keeps the track of other colliding keys.
+
+The third type keeps information about number of data portions
+for given key, the `X` keeps the track of other colliding keys.
+
+The fourth type keeps the data portion, the `j` indicates data portion number.
+
+The fifth type keeps the number of unique values of the key.
+
+The sixth type keeps the list of unique values of the key,
+the `i` indicates key number.
+
+See examples below to see use cases.
+
+---
+
+### EXAMPLES AND USECASES: ####################################################
+
+
+**EXAMPLE 1.** Basic use-case.
+ Creating macro hash table, macro `HT` is generated.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(HT)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Add elements to the `HT`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%HT(ADD,key=x,data=17)
+%HT(ADD,key=y,data=42)
+%HT(ADD,key=z,data=303)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Add some duplicates for the key x.
+ See macrovariables created.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%HT(ADD,key=x,data=18)
+%HT(ADD,key=x,data=19)
+
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Check the number od data portions in macrohash
+ for the key `x` and non existing key `t`.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put ##%HT(DP,key=x)##;
+%put ##%HT(DP,key=t)##;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Check the number od data portions in macrohash
+ for the key index 1 and 4.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put ##%HT(CHECKIDX,key=1)##;
+%put ##%HT(CHECKIDX,key=4)##;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Prints first data values for various keys.
+ Key `t` does not exist in the macrohash.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put #%HT(FIND,key=x)#;
+%put #%HT(FIND,key=y)#;
+%put #%HT(FIND,key=z)#;
+%put #%HT(FIND,key=t)#;
+
+%put #%HT(FIND,key=x,data=2)#;
+%put #%HT(FIND,key=x,data=3)#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Print first and subsequent data values
+ for a given KeyIDX. Index `4` does not exist.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put #%HT(KEYIDX,key=1)#;
+%put #%HT(KEYIDX,key=2)#;
+%put #%HT(KEYIDX,key=3)#;
+%put #%HT(KEYIDX,key=4)#;
+
+%put #%HT(KEYIDX,key=1,data=2)#;
+%put #%HT(KEYIDX,key=1,data=3)#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Print the key values for a given KeyIDX.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put #%HT(KEYVAL,key=1)#;
+%put #%HT(KEYVAL,key=2)#;
+%put #%HT(KEYVAL,key=3)#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Clear and delete macro hash table `HT`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%HT(CLEAR)
+%mcHashTable(HT,DELETE)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Combine `CHECK` and `FIND` methods
+ with macros `%array()` and `%do_over()`
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(H)
+%H(ADD,key=x,data=17)
+%H(ADD,key=x,data=18)
+%H(ADD,key=x,data=19)
+
+%array(A[%H(CHECK,key=x)]);
+
+%put %do_over(A, phrase=%nrstr(
+ %H(FIND,key=x,data=&_i_)
+), between = %str(,));
+
+%mcHashTable(H,delete)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Populate macro hash table from a dataset.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(CLASS)
+%let t = %sysfunc(datetime());
+data _null_;
+ set sashelp.class;
+ call execute('%CLASS(ADD,key=' !! name !! ',data=' !! age !! ')');
+ call execute('%CLASS(ADD,key=' !! name !! ',data=' !! weight !! ')');
+ call execute('%CLASS(ADD,key=' !! name !! ',data=' !! height !! ')');
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put _user_;
+%CLASS(CLEAR)
+
+
+%mcHashTable(CARS)
+%let t = %sysfunc(datetime());
+data _null_;
+ set sashelp.cars;
+ call execute('%CARS(ADD,key=' !! catx("|",make,model) !! ',data=' !! MPG_CITY !! ')');
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%* %put _user_;
+%CARS(CLEAR)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** Data portion may require quoting and un-quoting..
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(CODE)
+%CODE(CLEAR)
+%CODE(ADD,key=data, data=%str(data test; x = 42; run;))
+%CODE(ADD,key=proc, data=%str(proc print; run;))
+%CODE(ADD,key=macro,data=%nrstr(%put *****;))
+
+%CODE(FIND,key=data)
+%CODE(FIND,key=proc)
+%unquote(%CODE(FIND,key=macro))
+
+%mcHashTable(CODE,DELETE)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 5.** Longer lists.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%let size = 1000;
+
+%mcHashTable(AAA)
+%mcHashTable(BBB)
+%mcHashTable(CCC)
+%mcHashTable(DDD)
+
+%let t = %sysfunc(datetime());
+data _null_;
+ do i = 1 to &size.;
+ call execute(cats('%AAA(ADD,key=A', i, ',data=', i, ')'));
+ end;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=AAA_KEYSNUM;
+%AAA(CLEAR)
+
+%let t = %sysfunc(datetime());
+data _null_;
+ do i = 1 to &size.;
+ call execute(cats('%BBB(ADD,key=B', i, ',data=', i, ')'));
+ call execute(cats('%BBB(ADD,key=B', i, ',data=', i+1, ')'));
+ end;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=BBB_KEYSNUM;
+%BBB(CLEAR)
+
+%let t = %sysfunc(datetime());
+data _null_;
+ t= datetime();
+ do i = 1 to &size.;
+ call execute(cats('%CCC(ADD,key=C', i, ',data=', i, ')'));
+ end;
+ t = datetime() - t;
+ put t=;
+ t= datetime();
+ do i = 1 to &size.;
+ call execute(cats('%CCC(ADD,key=C', i, ',data=', i+1, ')'));
+ end;
+ t = datetime() - t;
+ put t=;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+
+%let t = %sysfunc(datetime());
+data test;
+ do i = 1 to &size.;
+ x = resolve(cats('%CCC(FIND,key=C', i, ',data=1)'));
+ y = resolve(cats('%CCC(FIND,key=C', i, ',data=2)'));
+ output;
+ end;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=CCC_KEYSNUM;
+%CCC(CLEAR)
+
+%let t = %sysfunc(datetime());
+data _null_;
+ do i = 1 to &size.;
+ call execute(cats('%DDD(ADD,key=D,data=', i, ')'));
+ end;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=DDD_KEYSNUM;
+%put %DDD(CHECK,key=D);
+%DDD(CLEAR)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 6.** Forbidden names.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable()
+%mcHashTable(_)
+
+%mcHashTable(ABCDEFGHIJKLMNOPQ) %* bad;
+%mcHashTable(ABCDEFGHIJKLMNOP) %* good;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**EXAMPLE 7.** Hashing algorithms.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcHashTable(H1,DCL,HASH=MD5)
+%mcHashTable(H2,DECLARE,HASH=CRC32)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---
+
+## >>> `%mcDictionary()` macro: <<< #######################
+
+The `%mcDictionary()` macro provided in the package
+is designed to facilitate the idea of a "macro dictionary"
+concept, i.e. *a list of macrovariables with common prefix
+and suffixes generated as a hash digest* which allows
+to use values other than integers as indexes.
+
+The `%mcDictionary()` macro allows to generate other macros
+which behaves like a dictionary. See examples below.
+
+The `%mcDictionary()` macro executes like a pure macro code.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcDictionary(
+ H
+ <,METHOD>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `H` - *Required*, a dictionary macro name and a declaration/definition,
+ e.g. `mcDictionary(HT)`. It names a macro which is generated by
+ the `%mcDictionary()` macro. Provided name cannot be empty
+ or an underscore (`_`). No longer than *16* characters.
+
+2. `METHOD` - *Optional*, if empty (or DECLARE or DCL) then the code of
+ a macro dictionary is compiled.
+ If `DELETE` then the macro dictionary named by `H` and all
+ macrovariables named like "`&H._`" are deleted.
+
+---
+
+### THE CREATED MACRO `%&H.()`: ####################################################
+
+The created macro imitates behaviour of a dictionary.
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%&H.(
+ METHOD
+ <,KEY=>
+ <,DATA=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `METHOD` - *Required*, indicate what behaviour should be executed.
+ Allowed values are:
+ - `ADD`, adds key and data portion to the macro dictionary,
+ *multiple data portions* are NOT available for one key.
+ - `FIND`, tests if given key exists in the macro dictionary
+ and, if yes, returns data value associated with the key.
+ For multiple data portions see the `data=` parameter.
+ - `CHECK`, returns indicator if the key exists in dictionary.
+ - `DEL`, removes key and data portion from the macro dictionary.
+ - `LIST`, prints out a dictionary to the log.
+ - `CLEAR` removes all data and keys values.
+
+* `KEY=` - *Optional*, provides key value for `ADD`, `FIND`, `CHECK`
+ and `DEL` methods.
+ Leading and trimming spaces are removed from the value.
+ The `MD5(...)` function is used to generate the hash.
+ Default value is `_`.
+
+* `DATA=` - *Optional*, provides data value for the `ADD` method.
+ Default value is blank.
+
+
+When macro is executed and when data are added the following types of
+*global* macrovariables are created:
+- `&H._########_K`,
+- `&H._########_V`,
+- `&H._KEYSNUM`.
+
+The `#` represents value generated by the `MD5(...)` function for the given key.
+
+The first type keeps information about the key.
+
+The second type keeps information about the value of a given key
+
+The third type keeps the number of unique values of the key.
+
+See examples below to see use cases.
+
+---
+
+### EXAMPLES AND USECASES: ####################################################
+
+
+**EXAMPLE 1.** Basic use-case.
+ Creating macro dictionary, macro `Dict` is generated.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcDictionary(Dict)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Add elements to the `Dict`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%Dict(ADD,key=x,data=17)
+%Dict(ADD,key=y y,data=42)
+%Dict(ADD,key=z z z,data=303)
+
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Add some duplicates for the key x.
+ See macrovariables created.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%Dict(ADD,key=x,data=18)
+
+%put _user_;
+
+%Dict(ADD,key=x,data=19)
+
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Check for the key `x` and non existing key `t`.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put ##%Dict(CHECK,key=x)##;
+%put ##%Dict(CHECK,key=t)##;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Prints data values for various keys.
+ Key `t` does not exist in the macrodictionary.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put #%Dict(FIND,key=x)#;
+%put #%Dict(FIND,key=y y)#;
+%put #%Dict(FIND,key=z z z)#;
+%put #%Dict(FIND,key=t)#;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ List dictionary content to the log.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%Dict(LIST);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Delete keys.
+ Key `t` does not exist in the macrodictionary.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%put #%Dict(DEL,key=z z z)#;
+%put _user_;
+%put #%Dict(DEL,key=t)#;
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Clear and delete macro dictionary `Dict`.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%Dict(CLEAR)
+%put _user_;
+
+%mcDictionary(Dict,DELETE)
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Populate macro dictionary from a dataset.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcDictionary(CLASS)
+%let t = %sysfunc(datetime());
+data _null_;
+ set sashelp.class;
+ call execute('%CLASS(ADD,key=' !! name !! ',data=' !! age !! ')');
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put _user_;
+%CLASS(CLEAR)
+
+
+%mcDictionary(CARS)
+%let t = %sysfunc(datetime());
+data _null_;
+ set sashelp.cars;
+ call execute('%CARS(ADD,key=' !! catx("|",make,model,type) !! ',data=' !! MPG_CITY !! ')');
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=CARS_KEYSNUM.;
+%CARS(LIST);
+%CARS(CLEAR)
+%put &=CARS_KEYSNUM.;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Data portion may require quoting and un-quoting..
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcDictionary(CODE)
+%CODE(CLEAR)
+%CODE(ADD,key=data, data=%str(data test; x = 42; run;))
+%CODE(ADD,key=proc, data=%str(proc print; run;))
+%CODE(ADD,key=macro,data=%nrstr(%put *****;))
+
+%CODE(FIND,key=data)
+%CODE(FIND,key=proc)
+%unquote(%CODE(FIND,key=macro))
+
+%CODE(LIST);
+
+%mcDictionary(CODE,DELETE)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** Longer lists.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%let size = 1000;
+
+%mcDictionary(AAA)
+
+%let t = %sysfunc(datetime());
+data _null_;
+ do i = 1 to &size.;
+ call execute(cats('%AAA(ADD,key=A', i, ',data=', i, ')'));
+ end;
+run;
+%put t = %sysevalf(%sysfunc(datetime()) - &t.);
+%put &=AAA_KEYSNUM;
+%AAA(CLEAR)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 5.** Forbidden names.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%mcDictionary()
+%mcDictionary(_)
+
+%mcDictionary(ABCDEFGHIJKLMNOPQ) %* bad;
+%mcDictionary(ABCDEFGHIJKLMNOP) %* good;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---
+
+## >>> `%QzipArrays()` macro: <<< #######################
+
+The zipArrays() and QzipArrays() macros
+allow to use a function on elements of pair of
+macro arrays.
+
+For two macroarrays the corresponding
+elements are taken and the macro applies a function, provided by user,
+to calculate result of the function on taken elements.
+
+When one of the arrays is shorter then elements are, by default,
+"reused" starting from the beginning. But this behaviour can be altered.
+See examples for the details.
+
+By default newly created macroarray name is concatenation
+of first 13 characters of names of arrays used to create the new one,
+e.g. if arrays names are `abc` and `def` then the result name is `abcdef`,
+if arrays names are `abcd1234567890` and `efgh1234567890` then the result
+name is `abcd123456789efgh123456789`
+
+The `zipArrays()` returns unquoted value [by `%unquote()`].
+The `QzipArrays()` returns quoted value [by `%superq()`].
+
+See examples below for the details.
+
+The `%QzipArrays()` macro executes like a pure macro code.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%QzipArrays(
+ first
+ ,second
+ <,function=>
+ <,operator=>
+ <,argBf=>
+ <,argMd=>
+ <,argAf=>
+ <,format=>
+ <,result=>
+ <,macarray=>
+ <,reuse=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `first` - *Required*, a space separated list of texts.
+
+2. `second` - *Required*, a space separated list of texts.
+
+* `function = cat` - *Optional*, default value is `cat`,
+ a function which will be applied
+ to corresponding pairs of elements of
+ the first and the second list.
+
+* `operator =` - *Optional*, default value is empty,
+ arithmetic infix operator used with elements
+ the first and the second list. The first
+ list is used on the left side of the operator
+ the second list is used on the right side
+ of the operator.
+
+* `argBf =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *before* elements the first list.
+ If multiple should be comma separated.
+
+* `argMd =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *between* elements the first list and
+ the second list.
+ If multiple should be comma separated.
+
+* `argAf =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *after* elements the second list.
+ If multiple should be comma separated.
+
+* `format=` - *Optional*, default value is empty,
+ indicates a format which should be used
+ to format the result, does not work when
+ the `operator=` is used.
+
+* `result=` - *Optional*, default value is empty,
+ indicates a name of newly created macroarray,
+ by default created macroarray name is concatenation
+ of first 13 characters of names of arrays used
+ to create the new one.
+
+* `macarray=N` - *Optional*, default value is `N`,
+ if set to `Y`/`YES` then a macro, named with
+ the array name, is compiled to create convenient
+ envelope for multiple ampersands, see the
+ `%array()` macro for details.
+
+* `reuse=Y` - *Optional*, default value is `Y`,
+ when one of the arrays is shorter then elements
+ are *reused* starting from the beginning.
+ If `CP` then function is executed on the *Cartesian
+ product* of arrays elements. Any other value will
+ cut the process with the end of the shorter array.
+ See examples for the details.
+
+### EXAMPLES AND USECASES: ####################################################
+
+See examples in `%zipArrays()` help for the details.
+
+---
+
+## >>> `%zipArrays()` macro: <<< #######################
+
+The zipArrays() and QzipArrays() macros
+allow to use a function on elements of pair of
+macro arrays.
+
+For two macroarrays the corresponding
+elements are taken and the macro applies a function, provided by user,
+to calculate result of the function on taken elements.
+
+When one of the arrays is shorter then elements are, by default,
+"reused" starting from the beginning. But this behaviour can be altered.
+See examples for the details.
+
+By default newly created macroarray name is concatenation
+of first 13 characters of names of arrays used to create the new one,
+e.g. if arrays names are `abc` and `def` then the result name is `abcdef`,
+if arrays names are `abcd1234567890` and `efgh1234567890` then the result
+name is `abcd123456789efgh123456789`
+
+The `zipArrays()` returns unquoted value [by `%unquote()`].
+The `QzipArrays()` returns quoted value [by `%superq()`].
+
+See examples below for the details.
+
+The `%zipArrays()` macro executes like a pure macro code.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%zipArrays(
+ first
+ ,second
+ <,function=>
+ <,operator=>
+ <,argBf=>
+ <,argMd=>
+ <,argAf=>
+ <,format=>
+ <,result=>
+ <,macarray=>
+ <,reuse=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `first` - *Required*, a space separated list of texts.
+
+2. `second` - *Required*, a space separated list of texts.
+
+* `function = cat` - *Optional*, default value is `cat`,
+ a function which will be applied
+ to corresponding pairs of elements of
+ the first and the second list.
+
+* `operator =` - *Optional*, default value is empty,
+ arithmetic infix operator used with elements
+ the first and the second list. The first
+ list is used on the left side of the operator
+ the second list is used on the right side
+ of the operator.
+
+* `argBf =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *before* elements the first list.
+ If multiple should be comma separated.
+
+* `argMd =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *between* elements the first list and
+ the second list.
+ If multiple should be comma separated.
+
+* `argAf =` - *Optional*, default value is empty,
+ arguments of the function inserted
+ *after* elements the second list.
+ If multiple should be comma separated.
+
+* `format=` - *Optional*, default value is empty,
+ indicates a format which should be used
+ to format the result, does not work when
+ the `operator=` is used.
+
+* `result=` - *Optional*, default value is empty,
+ indicates a name of newly created macroarray,
+ by default created macroarray name is concatenation
+ of first 13 characters of names of arrays used
+ to create the new one.
+
+* `macarray=N` - *Optional*, default value is `N`,
+ if set to `Y`/`YES` then a macro, named with
+ the array name, is compiled to create convenient
+ envelope for multiple ampersands, see the
+ `%array()` macro for details.
+
+* `reuse=Y` - *Optional*, default value is `Y`,
+ when one of the arrays is shorter then elements
+ are *reused* starting from the beginning.
+ If `CP` then function is executed on the *Cartesian
+ product* of arrays elements. Any other value will
+ cut the process with the end of the shorter array.
+ See examples for the details.
+
+### EXAMPLES AND USECASES: ####################################################
+
+**EXAMPLE 1.** Simple concatenation of elements:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(a[*] x1-x3 (1:3))
+%array(b[*] x1-x5 (11:15))
+
+%put _user_;
+
+%zipArrays(a, b);
+%put _user_;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Shorter list is "reused":
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(a[6] (1:6))
+%array(b[3] (10 20 30))
+
+%zipArrays(a, b, result=A_and_B, macarray=Y);
+%put %do_over(A_and_B);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 3.** Use of the `operator=`:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(c[0:4] (000 100 200 300 400))
+%array(d[2:16] (1002:1016))
+
+%zipArrays(c, d, operator=+, result=C_plus_D, macarray=Y);
+%put (%do_over(C_plus_D));
+
+%put %C_plus_D(1);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 4.** If one of array names is empty or an array does not exist:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(a[6] (1:6))
+%array(b[3] (10 20 30))
+
+%zipArrays(a, );
+%zipArrays(, b);
+
+%zipArrays(a, z);
+%zipArrays(z, b);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 5.** Use of the `function=`:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(one[3] A B C, vnames=Y)
+%array(two[5] p q r s t, vnames=Y)
+
+%zipArrays(
+ one
+,two
+,function = catx
+,argBf = %str( )
+,format = $quote.
+,macarray=Y
+)
+%put %do_over(onetwo);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 6.** To reuse or not to reuse, or maybe Cartesian product:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(e[3] (10 20 30))
+%array(f[2] (5:6))
+
+%zipArrays(e, f, reuse=n, operator=+, macarray=Y, result=_noReuse);
+%zipArrays(e, f, reuse=y, operator=+, macarray=Y, result=_yesReuse);
+%zipArrays(e, f, reuse=cp, operator=+, macarray=Y, result=_cartProdReuse);
+
+%put %do_over(_noReuse);
+%put %do_over(_yesReuse);
+%put %do_over(_cartProdReuse);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 7.** Use middle argument:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+%array(yr[3] (2018:2020))
+%array(mth[12] (1:12))
+
+%zipArrays(mth, yr, argMd=5, function=MDY, format=date11., macarray=Y);
+%put %do_over(mthyr);
+
+%zipArrays(mth, yr, argMd=5, function=MDY, format=date11., macarray=Y, reuse=cp);
+%put %do_over(mthyr);
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---
+
+## >>> `%sortMacroArray()` macro: <<< #######################
+
+The sortMacroArray() macro
+allow to sort elements of a macro array.
+
+The **limitation** is that sorted values are limited to 32767 bytes of length.
+
+See examples below for the details.
+
+### SYNTAX: ###################################################################
+
+The basic syntax is the following, the `<...>` means optional parameters:
+~~~~~~~~~~~~~~~~~~~~~~~sas
+%sortMacroArray(
+ array
+ <,valLength=>
+ <,outSet=>
+ <,sortseq=>
+)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+**Arguments description**:
+
+1. `array` - *Required*, name of an array generated by the `%array()` macro.
+
+* `valLength = 32767` - *Optional*, default value is `32767`,
+ maximum length of a variable storing macrovariable data.
+ (the reason of 32767 limitation)
+
+* `outSet = _NULL_` - *Optional*, default value is `_NULL_`,
+ an optional output dataset name.
+
+* `sortseq =` - *Optional*, default value is `LINGUISTIC(NUMERIC_COLLATION = ON)`,
+ sorting options for use in an internal `Proc SORT`.
+
+### EXAMPLES AND USECASES: ####################################################
+
+
+**EXAMPLE 1.** Basic use-case.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+
+options mprint;
+ods html;
+ods listing close;
+
+
+%array(hij [4:9] $ 512 ("C33" "B22" "A11" "A01" "A02" "X42"), macarray=Y)
+
+%put NOTE: %do_over(hij);
+
+%sortMacroArray(hij, valLength=3, outSet = A_NULL_(compress=char))
+
+%put NOTE: %do_over(hij);
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+**EXAMPLE 2.** Basic use-case.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~sas
+
+options mprint;
+ods html;
+ods listing close;
+
+
+%array(ds = sashelp.class, vars = name|NNN height|h, macarray=Y)
+%array(ds = sashelp.cars, vars = model|, macarray=Y)
+
+%put NOTE: %do_over(NNN);
+%put NOTE: %do_over(H);
+%put NOTE: %do_over(model);
+
+%sortMacroArray(NNN, valLength=30, outSet = A_NULL_(compress=char))
+%sortMacroArray(H, valLength=32)
+%sortMacroArray(model, valLength=120)
+
+%put NOTE: %do_over(NNN);
+%put NOTE: %do_over(H);
+%put NOTE: %do_over(model);
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+---
+
+## License ####################################################################
+
+Copyright (c) Bartosz Jablonski, since January 2019
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+---
diff --git a/hist/1.1.1/macroarray.zip b/hist/1.1.1/macroarray.zip
new file mode 100644
index 0000000..6bb2437
Binary files /dev/null and b/hist/1.1.1/macroarray.zip differ
diff --git a/macroarray.md b/macroarray.md
index fdeb46d..ca8b22d 100644
--- a/macroarray.md
+++ b/macroarray.md
@@ -19,7 +19,7 @@
---
-# The macroArray package [ver. 1.0.6] ###############################################
+# The macroArray package [ver. 1.1.1] ###############################################
The **macroArray** package implements a macro array facility:
- `%array()`,
@@ -75,10 +75,10 @@ Package contains:
Required SAS Components:
*Base SAS Software*
-*SAS package generated by generatePackage, version 20230905*
+*SAS package generated by generatePackage, version 20230904*
The SHA256 hash digest for package macroArray:
-`F*4FAAEE7DF2854EA31933AE017A89C1615C7291A66A07CCE345041EB0D587ED4E`
+`F*E9C0C58FB36AC40C76A518066B8C6F9942202A9DB2C2D737E95D2BB6E4ECED50`
---
# Content description ############################################################################################
diff --git a/macroarray.zip b/macroarray.zip
index 411c831..6bb2437 100644
Binary files a/macroarray.zip and b/macroarray.zip differ