Q: Can I use the %LOOKUP BIF to search an array of qualified data structures?
A: When the array is a simple array that's inside a data structure, you can search it with %LOOKUP in the same manner that you would a standalone field. It doesn't matter if it's qualified or not, as long as it's a simple array.
In many cases, people prefer to load their data into many arrays where each array is a subfield of a data structure. You can use %LOOKUP to search them, and you can use SORTA to sort them by different fields. There are examples of this in the ILE RPG Reference manual, so I won't include one here.
However, when the array is an array of data structures, it can't be used with the %LOOKUP BIF. (Perhaps that feature will be available in a future release of RPG!)
All %LOOKUP does is loop through an array and compare each array element to the value that you're searching for. It's easy enough to do the same thing by writing a simple loop, such as the one shown in the following code snippet:
D Wholesaler ds Dim(300) Qualified
D Name 20a
D Amount 12s 2
D found s 10I 0
D x s 10I 0
/free
Found = 0;
for x = 1 to %elem(Wholesaler);
if (Wholesaler(x).name = SearchArg);
Found = x;
leave;
endif;
endfor;
Once this code has run, the FOUND variable will contain the number of the array element where the wholesaler's name was the same as the value in the SearchArg field.
One of the newer features of the %LOOKUP BIF is that it can use a binary search algorithm when operating on a simple array. This is much faster than looping through each array element. This happens automatically when you specify the ASCEND or DESCEND keyword on the D- spec where the array is defined. The only "gotcha," however, is that you must sort the array before using the %LOOKUP BIF, or it'll return incorrect results.
You can do the same thing on an array of qualified data structures using two APIs. The qsort() API is used to sort the array, and the bsearch() API to perform the binary search.
In order to search data or to sort it, you have to be able to compare one element to another. That's easy for %LOOKUP to do because the compiler knows what sort of data you have in your program and can insert the code to do the comparison. It's not as easy with an API that's already been compiled and doesn't know anything about your program's definitions!
The solution was to ask the programmer to provide a routine that does the comparison. The programmer can then write a subprocedure. The subprocedure provides the code to compare the two array elements and returns a -1 when the first element should come first in the results, a 1 when the second one should come first, and a 0 when they're equal. You can then pass the API a pointer to this subprocedure so that it can call your routine whenever it needs to make a comparison.
The following code demonstrates creating a simple telephone directory as an array of qualified data structures. It uses the qsort() API to sort the array by last name and the bsearch() API to look those names up using the binary search algorithm:
H DFTACTGRP(*NO) BNDDIR('QC2LE')
D qsort PR extproc('qsort')
D base * value
D num 10U 0 value
D width 10U 0 value
D compare * procptr value
D bsearch PR * extproc('bsearch')
D key * value
D base * value
D num 10U 0 value
D size 10U 0 value
D compare * procptr value
D myTemplate ds qualified
D based(Template)
D LastName 20A
D FirstName 20A
D ext 10I 0
D users ds likeds(myTemplate)
D dim(100)
D p_match s *
D match ds likeds(myTemplate)
D based(p_match)
D key ds likeds(myTemplate)
D CompByLast pr 10I 0
D elem1 likeds(myTemplate)
D elem2 likeds(myTemplate)
D x s 10I 0
D numUsers s 10I 0
D msg s 52A
/free
// -------------------------------------------
// Create some sample data
// -------------------------------------------
x = 1;
users(x).LastName = 'Klement';
users(x).FirstName = 'Scott';
users(x).ext = 292;
x = x + 1;
users(x).LastName = 'Lewis';
users(x).FirstName = 'Doug';
users(x).ext = 280;
x = x + 1;
users(x).LastName = 'Bizub';
users(x).FirstName = 'James';
users(x).ext = 291;
x = x + 1;
users(x).LastName = 'Michuda';
users(x).FirstName = 'Michael';
users(x).ext = 209;
x = x + 1;
users(x).LastName = 'Solano';
users(x).FirstName = 'Maria';
users(x).ext = 216;
x = x + 1;
users(x).LastName = 'Straw';
users(x).FirstName = 'Penny';
users(x).ext = 302;
x = x + 1;
users(x).LastName = 'Wiesner';
users(x).FirstName = 'Beatrice';
users(x).ext = 200;
x = x + 1;
users(x).LastName = 'Vogl';
users(x).FirstName = 'Jackie';
users(x).ext = 201;
x = x + 1;
users(x).LastName = 'Sotski';
users(x).FirstName = 'Daniel';
users(x).ext = 203;
numUsers = x;
// -------------------------------------------
// Sort array by Last name
// -------------------------------------------
qsort( %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByLast) );
// -------------------------------------------
// Search for 'Klement'
// then for 'Michuda'
// -------------------------------------------
key.LastName = 'Klement';
p_match = bsearch( %addr(key)
: %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByLast) );
if (p_match = *NULL);
msg = %trimr(key.lastname) + ' not found!';
dsply msg;
else;
msg = %trimr(match.lastname) + ' is ext '
+ %char(match.ext);
dsply msg;
endif;
key.LastName = 'Michuda';
p_match = bsearch( %addr(key)
: %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByLast) );
if (p_match = *NULL);
msg = %trimr(key.lastname) + ' not found!';
dsply msg;
else;
msg = %trimr(match.lastname) + ' is ext '
+ %char(match.ext);
dsply msg;
endif;
*inlr = *on;
/end-free
*++++++++++++++++++++++++++++++++++++++++++++++++++++
* Compare Two Elements, using Last Name as the
* only key.
*++++++++++++++++++++++++++++++++++++++++++++++++++++
P CompByLast B
D CompByLast PI 10I 0
D elem1 likeds(myTemplate)
D elem2 likeds(myTemplate)
/free
select;
when (elem1.LastName < elem2.LastName);
return -1;
when (elem1.LastName > elem2.LastName);
return 1;
other;
return 0;
endsl;
/end-free
P E
Since you control the way things are compared, you have a bit more flexibility than you would with either SORTA or %LOOKUP.
For example, just by changing out the compare routine, you can have it search by both first and last name and, a few lines later in the same program, you can switch to searching by extension number. The following code demonstrates this. (I've omitted the D-specs and code to populate the sample array, since it's the same as the code in the previous section.)
// -------------------------------------------
// How about searching by complete name
// (first & last)
// -------------------------------------------
qsort( %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByFirst) );
key.FirstName = 'Scott';
key.LastName = 'Klement';
p_match = bsearch( %addr(key)
: %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByFirst) );
if (p_match = *NULL);
msg = %trimr(key.firstname) + ' '
+ %trimr(key.lastname) + ' not found!';
dsply msg;
else;
msg = %trimr(match.firstname) + ' '
+ %trimr(match.lastname) + ' is ext '
+ %char(match.ext);
dsply msg;
endif;
// -------------------------------------------
// How about searching by extension number
// -------------------------------------------
qsort( %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByExt) );
key.ext = 291;
p_match = bsearch( %addr(key)
: %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompByExt) );
if (p_match = *NULL);
msg = %char(key.ext) + ' not found!';
dsply msg;
else;
msg = %trimr(match.firstname) + ' '
+ %trimr(match.lastname) + ' is ext '
+ %char(match.ext);
dsply msg;
endif;
*INLR = *ON;
/end-free
*++++++++++++++++++++++++++++++++++++++++++++++++++++
* Compare Two Elements, using a composite key
* created from the first & last name
*++++++++++++++++++++++++++++++++++++++++++++++++++++
P CompByFirst B
D CompByFirst PI 10I 0
D elem1 likeds(myTemplate)
D elem2 likeds(myTemplate)
/free
select;
when (elem1.FirstName < elem2.FirstName);
return -1;
when (elem1.FirstName > elem2.FirstName);
return 1;
when (elem1.LastName < elem2.LastName);
return -1;
when (elem1.LastName > elem2.LastName);
return 1;
other;
return 0;
endsl;
/end-free
P E
*++++++++++++++++++++++++++++++++++++++++++++++++++++
* Compare Two Elements, using the telephone ext
* as the key
*++++++++++++++++++++++++++++++++++++++++++++++++++++
P CompByExt B
D CompByExt PI 10I 0
D elem1 likeds(myTemplate)
D elem2 likeds(myTemplate)
/free
select;
when (elem1.Ext < elem2.ext);
return -1;
when (elem1.Ext > elem2.ext);
return 1;
other;
return 0;
endsl;
/end-free
P E
But wait... there's more! Again, since you can control the way things are compared, you could make it case-insensitive if you wanted to, which %LOOKUP can't do. In the following example, I've done a case- insensitive search by the last name:
// -------------------------------------------
// You can also do a case-insensitive sort
// and search just by changing the
// way the elements are compared
// -------------------------------------------
qsort( %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompCase) );
key.LastName = 'stRaW';
p_match = bsearch( %addr(key)
: %addr(users)
: numUsers
: %size(myTemplate)
: %paddr(CompCase) );
if (p_match = *NULL);
msg = %trimr(key.lastname) + ' not found!';
dsply msg;
else;
msg = %trimr(match.firstname) + ' '
+ %trimr(match.lastname) + ' is ext '
+ %char(match.ext);
dsply msg;
endif;
*inlr = *on;
/end-free
*++++++++++++++++++++++++++++++++++++++++++++++++++++
* This is the same as CompByLast, except that it's
* not case-sensitive.
*++++++++++++++++++++++++++++++++++++++++++++++++++++
P CompCase B
D CompCase PI 10I 0
D elem1 likeds(myTemplate)
D elem2 likeds(myTemplate)
D upper c
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
D lower c
'abcdefghijklmnopqrstuvwxyz'
D last1 s like(myTemplate.lastname)
D last2 s like(myTemplate.lastname)
/free
last1 = %xlate(lower:upper: elem1.lastname);
last2 = %xlate(lower:upper: elem2.lastname);
select;
when (last1 < last2);
return -1;
when (last1 > last2);
return 1;
other;
return 0;
endsl;
/end-free
P E