Macro assoc

From sasCommunity
Jump to: navigation, search

SAS-L link

  • Date: Wed, 13 Sep 2000 18:30:03 -0500
  • From: "Dwight Eggers (EUS)
  • Subject: SAS Tip: Macro for associative array functionality

The SAS macro language does not support arrays directly, but there are coding techniques that provide primitive array-like functionality. One technique is to define macro variable names with a common root and append a sequential integer suffix. Another technique is to populate a macro variable with a list of values, then extract individual elements of this list using the %scan function.

This tip extends the convention of using lists in macro variables as pseudo arrays with the introduction of a user-defined macro function, %ASSOC, to directly resolve the association between elements in corresponding positions in two lists. This functionality is somewhat analogous to the associative array variable type available in some modern scripting languages, like Perl and JavaScript, which is the basis for the function name.

The following describes the functionality, gives examples of its usage, and conclude with the source code. Simple test cases are appended at the end of the source code to validate the response and provide additional examples.

This tip grew out of a discussion with Frank Poppe, following a question he posed to SAS-L around August 20 about techniques for customizing annotations based on the DFLANG option. His example is used below. Frank also provided valuable suggestions on ways to clean up the original version of this function, and his contribution is gratefully acknowledged.

Function Description

Consider a text string that represent a list of elements

 [element1] [element2] [element3] ...

where a space delimits the elements.

Next extend this to two lists

 [key1] [key2] [key3] ...
 [val1] [val2] [val3] ...

where the elements are associated between lists according to their position in the list

 [key1] :: [val1]
 [key2] :: [val2]
   ...

If these lists are contained in macro variables, KEYLIST and VALULIST, the %ASSOC function

 %assoc ([key], &keylist, &valulist)

resolves to the element from VALULIST that is in the position that corresponds to the position of [key] in KEYLIST.

If the value [key] cannot be found in the keylist elements, then the function returns a special text string as represented by the UNRESOLV keyword parameter. The default is '<unresolved>'.

By default, a space constitutes the delimiter between elements. If it is necessary for the key or value elements to contain spaces, or for the value elements to contain null values, then an alternate delimiter is specified through the DELIM keyword. The delimiter character must be common to both lists. It is not necessary to specify the delimiter explicitly if it is used in the first position of the key list. Valid delimiters are the same as recognized by the %scan function, except for the '%' and '&' characters.

Other features include the following:

The key elements must be unique and not null.

A null value is represented by a space. For example, the lists

 %let varlist=|A|CVAR|DVAR ;
 %let fmtlist=| |$12.|DATE9. ;

represents a list of variable names and formats with the first element of the format list representing an unspecified format for variable A. The reference

 %assoc (A, &varlist, &fmtlist)

will return a null value.

The key value and key list are case insensitive. Both are converted to upper case before checking for the existence of the specified key in the key list.

If a comma is used as the delimiting character, it is necessary to %quote the two list positional parameters in the macro invocation.

The list elements should not contain '%' or '&' characters unless it is intended for these to be expanded through the macro facility. If so, then these should be passed with an %nrstr function in the macro invocation (see Test Case 4 -- indirect referencing).

I leave more exotic quoting situations to the more seasoned macro experts.


Examples

1) Frank Poppe's language translation problem: Given an application for which it is desired to customize the output annotations according to the language in option DFLANG, how can this be done without resorting to macro logic which would require placing the output step within a macro?

%* list of languages ;
%let langlist=English Dutch Swedish Italian;
%* words needing translation ;
%let result=Result Resultaat Resultat Risultare;
%let graph=Graph Grafiek Grafen Grafico;

The reference

%sysfunc(getoption(DFLANG))

will resolve to the current language.

Using this in conjunction with the %ASSOC function to make a translation:

%assoc (%sysfunc(getoption(DFLANG)), &langlist, &result)

will resolve to the representation of 'Result' in the current language.

%assoc (%sysfunc(getoption(DFLANG)), &langlist, &graph)

will resolve to the representation of 'Graph' in the current language.


2) Dataset Attributes: Given a dataset, what are the attributes associated with its variables?

Use the distribution sample dataset SASHELP.RETAIL as an example. The variables of this dataset and their corresponding attributes can be placed in macro variable lists with the following PROC SQL step.

proc sql noprint;
  select '|'||name,
         '|'||type,
         '|'||length,
         '|'||label,
         '|'||format,
         '|'||informat
  into :namelist separated by ' ',
       :typelist separated by ' ',
       :lenglist separated by ' ',
       :labllist separated by ' ',
       :fmtlist  separated by ' ',
       :ifmtlist separated by ' '
  from sashelp.vcolumn
  where libname='SASHELP'
    and memname='RETAIL'
    and memtype='DATA'
  order by npos
  ;

[suggest use dictionary.columns -- RJF2]

This populates the list variables in a form suitable for %assoc. The prefix '|' and "separated by ' '" are needed to accommodate the occurrence of blank attributes.

With these lists populated, then any attribute of a given variable can easily be extracted from the corresponding list. For example

 %assoc (date, &namelist, &fmtlist)

resolves to the format attribute for variable DATE,

 %assoc (sales, &namelist, &lbllist)

resolves to the label attribute for variable SALES.

This basic functionality is useful to construct high level utility macros to operate on a specified dataset.

There are simple test cases appended to end of the source code, to validate the functionality and describe some other variations of its use.

This code has been tested under versions 6.12 and 8.1 on Sun Solaris unix operating system.

Macro Assoc

%macro assoc 
(key 
,keylist 
,valulist 
,delim=%str( ) 
,unresolv=%str(<unresolved>));

%* This is a user-defined function that provides associative-array     ;
%* type referencing.  Calling parameters are the following:            ;

%*   key      -- value of key to be scanned for in the key list.       ;
%*   keylist  -- list of key values, separated by a space or other     ;
%*               specified delimiter.                                  ;
%*   valulist -- list of values associated with the keys, with values  ;
%*               in the same order as their corresponding key.         ;
%*   delim    -- character used to delimit elements of the two lists.  ;
%*               The default is a space.  It is not necessary to       ;
%*               specify the delimiter if a standard delimiter occurs  ;
%*               in the first position of the key list.                ;
%*   unresolv -- text to return if the key does not occur in the key   ;
%*               list.                                                 ;

%* The key parameters are case insensitive -- both are converted to    ;
%* upper case before the comparision.                                  ;

%* Author: Dwight Eggers, 11/18/99, with enhancements by Frank Poppe,  ;
%* 8/23/00.                                                            ;

%local i comp dlm ;

%* Check for a standard delimiter character in the first position of  ;
%* the key list -- if found, this overrides the delimiter identified  ;
%* as a calling parameter.                                            ;
%let dlm = %substr(%quote(&keylist) ,1 ,1) ;
%let i=%index ( %str( .<%(+%&!$*%);^-/,%%|) , &dlm ) ;
%if &i = 0 %then %let dlm = &delim ;

%let i=0;

%* determine the position of KEY within the key list ;
%do %until(   %upcase( &key ) = %upcase( &comp ) 
           or &comp = %str() );
    %let i = %eval ( &i + 1 );
    %let comp = %scan ( %quote(&keylist)  , &i, &dlm );
    %end;

%* return the corresponding segment from the value list ;
%if &comp = %str() %then &unresolv ;
%else %trim ( %scan ( %quote(&valulist) , &i , &dlm ) ) ;

%mend assoc;

Testing

The following code is used to test ASSOC ;

%* Case 1: basic space-delimited case;
%let names =A B C;
%let labels=Dogs Cats Goats;
%put Case 1.1: %assoc (a, &names, &labels);
%put Case 1.2: %assoc (b, &names, &labels);
%put Case 1.3: %assoc (c, &names, &labels);
%* erroneous references;
%put Case 1.4: %assoc (d, &names, &labels);

%* Case 2: attribute segments with imbedded spaces and nulls;
%* Also test case-insensitivity of key/keylist ;
%let names =A|b|c;
%let labels=Label A| |Label C;
%put Case 2.1: %assoc (a, &names, &labels, delim=%str(|) );
%put Case 2.2: %assoc (B, &names, &labels, delim=%str(|) );
%put Case 2.3: %assoc (C, &names, &labels, delim=%str(|) );

%* Case 3: erroneous inbalance of length of lists;
%let names =A B C D;
%let labels=Dogs Cats Goats;
%put Case 3.1: %assoc (d, &names, &labels);

%* Case 4: indirect referencing by passing macro variables in attribute list
;
%* (Note the need for the NRSTR function so that this list will not be
resolved prematurely) ;
%let names =A B C ;
%let labels=%nrstr(&Dog &Cat &Goat);
%let dog=Snoopy;
%let cat=Garfield;
%let goat=Bill Gates;
%put Case 4.1: A -> %assoc (a, &names, &labels);
%put Case 4.2: B -> %assoc (b, &names, &labels);
%put Case 4.3: C -> %assoc (c, &names, &labels);

%* Case 5: self referencing -- to determine whether KEY is in the key list ;
%let names =A B C ;
%put Case 5.1: %assoc (A, &names, &names, unresolv=!MISSING! ) ;
%put Case 5.2: %assoc (D, &names, &names, unresolv=!MISSING! );

%* Case 6: delimiter passed implicitly in key list ;
%* Also an example of multiple attributes associated with a list of variable
names ;
%let names =%str(|A |B |C );
%let types =%str(|C |C |N );
%let lengths = %str (|20 |200 |8 );
%let formats = %str (|$20. |$100. |DOLLAR10.2 );
%put Case 6.1: Variable A: %assoc (A, &names, &types) / %assoc (A, &names,
&lengths) / %assoc (A, &names, &formats) ;
%put Case 6.2: Variable C: %assoc (C, &names, &types) / %assoc (C, &names,
&lengths) / %assoc (C, &names, &formats) ;

%* Case 7: lists with comma delimiters;
%let names =A,B,C;
%let labels=Dogs,Cats,Goats;
%put Case 7.1: %assoc (a, %quote(&names), %quote(&labels), delim=%str(,));
%* erroneous call, without quoting ;
%put Case 7.2: %assoc (a, &names, &labels, delim=%str(,));

harvested from SAS-L by: --macro maven == the radical programmer 16:41, 27 October 2008 (EDT)