Guida all'utilizzo delle schiere nei programmi RPG

La gestione delle schiere RPG è cambiata notevolmente
nel corso degli ultimi anni, sia per quanto
riguarda la definizione sia per quello che concerne
l’elaborazione. Esistono numerose varianti delle
vecchie schiere a tempo di esecuzione. Oltre alle
semplici schiere di campi è ora possibile definire schiere di
strutture dati, schiere qualificate, schiere sovrapposte, schiere
multidimensionali, schiere allocate dinamicamente e strutture
di dati a occorrenza multipla. Ogni tipo di schiera presenta
vantaggi e svantaggi che possono essere trascurabili o determinanti;
alcune richiedono un’inizializzazione esplicita, altre
no; alcune possono essere ordinate, altre no.
In questo articolo cercheremo di approfondire la nostra conoscenza
delle schiere a tempo di esecuzione. Spiegherò come
definire e inizializzare ogni tipo di schiera, come accedere agli
elementi in essa contenuti, come ordinarla e come effettuare
delle ricerche al suo interno. Per una serie di utili suggerimenti
sulle specifiche tecniche di codifica, si consulti il box “Sette
consigli sulle schiere”. Ma cominciamo dalle basi.
Elementi basilari
Una schiera è un insieme di elementi distinti che condividono
una definizione comune dei dati. Una schiera può contenere
fino a 32.767 elementi dalla lunghezza massima di
65.535 byte.
Le schiere sono codificate nelle specifiche D come campi
autonomi o come sottocampi di strutture dati. La parola chiave
DIM definisce il numero di elementi di una schiera. Le
schiere autonome sono limitate dall’allocazione di memoria di
un programma di 16.776.704 byte, mentre le schiere di sottocampi sottocampi
dall’allocazione di memoria di una struttura dati:
9.999.999 byte per le strutture dati prive di nome e 65.535 byte
per le strutture dati con nome.
Le schiere vengono elaborate nelle specifiche in formato
libero del programma. Per accedere a un elemento della schiera,
è necessario utilizzare un valore intero che funge da indice,
mentre per accedere a tutti gli elementi della schiera, si deve
specificare un asterisco come indice. Con le schiere è possibile
utilizzare i seguenti BIF (Built-In Functions = funzioni integrate)
e codici operativi:
• %ELEM restituisce il numero di elementi di una schiera;
• %LEN restituisce la lunghezza di un elemento della
schiera;
• %SIZE restituisce la dimensione di un elemento della
schiera;
• %LOOKUP, %LOOKUPLT, %LOOKUPLE,
%LOOKUPGT e %LOOKUPGE cercano un elemento in
una schiera;
• %SUBARY estrae un sottoinsieme da una schiera
(disponibile con la V5R3);
• %XFOOT somma una schiera numerica;
• SORTA ordina una schiera.
Di seguito verranno presentati degli esempi dei diversi tipi
di schiere utilizzando le seguenti definizioni:
d idx s 10i 0
d item ds qualified
d id 10i 0
d desc 30
IDX definisce un indice di schiera come un intero di 10 cifre.
La struttura di dati ITEM definisce un ID articolo e un descrizione
su cui si basano le definizioni delle schiere.
Schiere semplici
Una schiera semplice è un campo autonomo contenente un
certo numero di elementi:
d items s like( item.id )
d dim( 99 )
La schiera ITEMS contiene 99 elementi simili a ITEM.ID.
La schiera viene automaticamente inizializzata a tempo di
esecuzione con degli zero in quanto ITEM.ID è un campo
numerico.

Si utilizza un indice per accedere a un elemento della
schiera:
items( idx ) = item.id;
item.id = items( idx );
È inoltre possibile ordinare la schiera:
sorta items;
e cercare degli elementi:
item.id = 123;
idx = %lookup( item.id: items );
Sia la funzione SORTA sia la funzione %LOOKUP lavorano
a livello di intera schiera. Se tuttavia è stata caricata solo
parte della schiera, per esempio i primi 20 elementi, sarà possibile
limitare l’ambito di queste funzioni nel modo seguente:

sorta %subarr( items: 1: 20 );
idx = %lookup( item.id: items: 1: 20 );
Le schiere semplici presentano solo una limitazione importante.
Si supponga di voler definire una schiera DESCS
affinché contenga le descrizioni degli articoli contenuti in
ITEMS. Si caricano gli articoli e le relative descrizioni rispettivamente
nelle schiere ITEMS e DESCS. Se a questo
punto ITEMS viene ordinata, gli elementi della schiera non
saranno più nella stessa sequenza di quelli della schiera
DESCS.
Schiere di strutture dati
Una schiera di strutture dati è una schiera autonoma contenente
un certo numero di elementi, ciascuno dei quali è una struttura
dati:
d items s like( item )
d dim( 99 )

La schiera ITEMS contiene 99 elementi simili alla struttura
dati ITEM. In questo tipo di schiera non esistono i sottocampi
ID e DESC. La schiera viene automaticamente inizializzata
con degli spazi vuoti a tempo di esecuzione. Per evitare che si
verifichino degli errore con i dati decimali quando si fa riferimento
all’area ITEM.ID di un elemento, è necessario inizializzare
esplicitamente la schiera:
clear item;
items = item;
Vengono utilizzati sia un indice sia una struttura di dati per accedere
ai componenti di un elemento della schiera:
item = items( idx );
id = item.id;
È possibile ordinare la schiera:
sorta items;
e cercare elementi, purché vengano specificati i valori di entrambi
i sottocampi nella struttura dati ITEM:
item.id = 123;
item.desc = ‘AGGEGGIO’;
idx = %lookup( item: items );
Cercare un ITEM.ID nella schiera risulta un po’ più laborioso.
Innanzitutto è necessario aggiungere la parola chiave
ASCEND alla definizione della schiera e definire una struttura
dati contenente la chiave per la ricerca:
d items s like( item )
d dim( 99 )
d ascend
d key ds likeds( item )
Quindi la schiera viene ordinata e si utilizza %LOOKUPGE
per trovare una corrispondenza parziale per un ITEM.ID:
sorta items;
clear key;
key.id = 123;
idx = %lookupge( key: items );
if idx > 0;
item = items( idx );
if key.id = item.id;
// trovato!
endif;
endif;

Schiere qualificate
Una schiera qualificata viene codificata come un sottocampo
in una struttura dati qualificata:
d items ds qualified
d inz
d array likeds( item )
d dim( 99 )
La schiera ITEMS.ARRAY contiene 99 elementi simili alla
struttura dati ITEM. La schiera contiene i sottocampi ID e
DESC a cui è possibile fare riferimento direttamente. Gli elementi
della schiera vengono inizializzati automaticamente a
tempo di esecuzione specificando la parola chiave INZ nella
definizione della struttura dati.
È possibile utilizzare un indice per accedere a un elemento
completo della schiera:
item.id = 123;
items.array( idx ) = item;
o a uno dei suoi sottocampi:
items.array( idx ).id = 123;

Le schiere qualificate forniscono un metodo naturale per
definire schiere di strutture dati. Sfortunatamente, non è possibile
ordinare né cercare elementi in una schiera contenente un
nome qualificato.
Schiere sovrapposte
Le schiere sovrapposte vengono codificate sotto forma di sottocampi
in una struttura dati:
d arrfays ds inz
d items like( item )
d dim( 99 )
d ids like( item.id )
d overlay( items: 1 )
d descs like( item.desc )
d overlay( items: *next )
La schiera ITEMS contiene 99 elementi simili alla struttura
dati ITEM. Tale schiera viene in seguito ridefinita sotto forma
di due schiere separate per IDS e DESCS utilizzando la parola
chiave OVERLAY. Le singole schiere vengono quindi inizializzate
automaticamente a tempo di esecuzione specificando
la parola chiave INZ nella definizione della struttura dati.
È possibile utilizzare un indice per accedere a un elemento
completo della schiera:
item.id = 123;
items( idx ) = item;
o a una delle sue schiere sovrapposte:
ids( idx ) = 123;
È possibile ordinare l’intera schiera direttamente:
sorta items;
o metterla in sequenza utilizzando i valori di una delle sue
schiere sovrapposte:
sorta ids;
L’ordinamento della schiera IDS in realtà ordina la schiera
ITEMS in base alla sequenza di ID, preservando la relazione
tra gli identificativi degli articoli e la rispettiva descrizione.
È anche possibile cercare degli elementi nelle schiere
sovrapposte:
idx = %lookup( item.id: ids );
Schiere multidimensionali
Le schiere multidimensionali sono state introdotte nell’RPG
con la V5R2. Vengono codificate come schiere di schiere utilizzando
le strutture dati qualificate:
d items ds qualified
d inz

d array likeds( data )
d dim( 99 )
d data ds qualified
d id like( item.id )
d sales 9p 2 dim( 12 )
ITEMS.ARRAY è una schiera bidimensionale contenente i
valori di vendita degli articoli per ogni mese dell’anno. La
prima dimensione contiene gli articoli, mentre la seconda i
valori di vendita. La schiera viene codificata utilizzando
due strutture dati. La struttura dati ITEMS definisce una
schiera di 99 elementi simili alla struttura dati DATA, che
contiene un ID articolo e una schiera di 12 valori di vendita
per i mesi dell’anno. Gli elementi della schiera vengono inizializzati
automaticamente a tempo di esecuzione specificando
la parola chiave INZ nella definizione della struttura
dati ITEMS.
Viene utilizzato un indice per ogni dimensione della
schiera:
idx = 1;
id = items.array( idx ).id;
idx2 = 12;
decSales = items.array( idx ).sales( idx2 );
È possibile codificare schiere con più di due dimensioni, ma
due o tre dimensioni sono sufficienti nella maggior parte dei
casi. L’intera schiera è contenuta nella struttura dati qualificata
di livello superiore, che può essere solo di 64 K. Ciò significa
che all’aumentare del numero delle dimensioni, lo
spazio a disposizione di ogni dimensione si fa sempre più
ristretto.
Schiere allocate dinamicamente
Finora abbiamo passato in rassegna schiere statiche la cui memoria
viene definita in fase di compilazione e che vengono allocate
quando il programma viene caricato in memoria. Le
schiere statiche si rivelano particolarmente adatte per le schiere
piccole o per quelle più grandi ma normalmente piene. Tuttavia,
alcune applicazioni richiedono schiere che presentano
dimensioni diverse in momenti diversi. Per esempio, un ordine
di vendita conterrà normalmente solo alcuni elementi, ma
nel caso di un ordine tra aziende potrebbe contenere migliaia
di righe. In questa situazione, è consigliabile modificare dinamicamente
la dimensione delle schiere degli ordini in modo
da poter utilizzare la memoria in maniera più efficiente.
Per allocare dinamicamente la memoria a tempo di esecuzione
per una schiera, si utilizza una variabile based e quindi
si controlla la memoria assegnata alla variabile. Il processo è
molto semplice. Dapprima, si definisce la schiera sulla base di
un puntatore:
d items s like( item.id )
d dim( 32000 )
d based( itemsPtr )

La schiera ITEMS contiene fino a 32.000 ITEM.IDS. Non
viene assegnata alcuna memoria per la schiera in quanto questa
è basata sul puntatore ITEMSPTR.
Quindi, si definiscono le variabili che controllano l’allocazione
della memoria:
d itemsCurr s like( idx )
d itemsSize s like( idx )
d itemsIncr c 100
ITEMSCURR contiene il numero corrente di elementi della
schiera, ITEMSSIZE contiene la dimensione corrente della
schiera e ITEMSINCR definisce il numero di elementi per cui
allocare memoria ogni volta.
Per semplificare il processo, è consigliabile allocare un
byte di memoria per la schiera nella sezione di inizializzazione
del programma:
itemsPtr = %alloc( 1 );
Le schiere allocate dinamicamente devono essere inizializzate
esplicitamente. Il modo più semplice per fare ciò consiste
nel caricare elementi in sequenza facendo quindi riferimento
solo a quelli caricati. Prima di caricare un elemento, è necessario
verificare se la schiera richiede un’estensione:
if itemsCurr = itemsSize;
itemsSize += itemsIncr;
itemsPtr = %realloc( itemsPtr: itemsSize *
%size( items ) );
endif;
Se la schiera è piena, se ne incrementa la dimensione e si
cambia la memoria in base alla nuova dimensione della schiera.
Quindi si carica l’elemento:
itemsCurr += 1;
items( itemsCurr ) = 123;
È possibile ordinare e cercare elementi nella schiera, purché
si restringano le funzioni alla parte caricata della schiera:

sorta %subarr( items: 1: itemsCurr );
idx = %lookup( item.id: items: 1: itemsCurr );
Alla fine del programma è consigliabile liberare la memoria
allocata per la schiera:
dealloc itemsPtr;
Strutture dati ad occorrenza multipla
Una struttura dati ad occorrenza multipla è un tipo alternativo
di schiera di record:
d itemSaleData e ds extname( itemSale )
d prefix( sal_ )
d occurs( 99 )
d inz
La struttura dati ITEMSALEDATA definisce 99 occorrenze
del formato record nel file ITEMSALE. I sottocampi della
struttura dati presentano il prefisso SAL_. Le occorrenze
della struttura dati vengono automaticamente inizializzate a
tempo di esecuzione specificando la parola chiave INZ nella
definizione.
Si utilizza %OCCUR per accedere a un’occorrenza della
struttura dati:
%occur( itemSaleData ) = 1;
sal_item = 123;
Il codice riportato sopra assegna un valore all’articolo nella
prima occorrenza della struttura dati.
Non è possibile utilizzare nessuna delle funzioni standard
delle schiere con le strutture dati ad occorrenza multipla. Allora
perché utilizzarle quando le schiere sono più flessibili? Perché
talvolta, l’RPG ci costringe ad utilizzarle; per esempio è necessario
utilizzare una struttura dati ad occorrenza multipla come
struttura ospite per reperire righe multiple da un cursore SQL.
A voi la scelta
In questo articolo abbiamo passato in rassegna sette diversi tipi
di schiere a tempo di esecuzione: le schiere semplici, le schiere
di strutture dati, le schiere qualificate, le
schiere sovrapposte, le schiere multidimensionali,
le schiere allocate dinamicamente e
le strutture dati a occorrenza multipla. Ciascuno
di questi tipi presenta naturalmente
dei vantaggi e degli svantaggi. Le differenze
principali riguardano l’inizializzazione,
il supporto di SORT e %LOOKUP e le restrizioni
a livello di dimensione. È possibile
utilizzare lo schema illustrato nella figura
1 per scegliere il tipo di schiera più adatto
alle proprie esigenze.

Sette consigli sulle schiere

Quando si lavora con le schiere, è sempre utile avere a disposizione
tecniche diverse da utilizzare all’occorrenza. I consigli
che seguono consentono di semplificare la gestione delle
schiere.
1. Utilizzate le definizioni LIKE e LIKEDS. Per rendere il codice
più facile da leggere e gestire, utilizzate, nelle specifiche D, le
parole chiavi LIKE e LIKEDS per definire gli elementi della
schiera:
d items s like( item.id )
d dim( 99 )
2. Utilizzate indici interi. Utilizzate tipi di dati interi per gli indici
delle schiere:
d idx s 10i 0
Gli indici interi sono molto più veloci da elaborare rispetto agli
indici numerici impaccati o a zonatura.
3. Utilizzate %ELEM per controllare le condizioni limite. Utilizzate
sempre %ELEM per controllare le condizioni limite della
schiera anziché codificare il numero degli elementi:
for idx = 1 to %elem( items );
// elabora
endfor;

4. Evitate inizializzazioni non necessarie. Inizializzare ripetutamente
schiere di grandi dimensioni è costoso. Provate a caricare
le schiere in sequenza tenendo il conto del numero corrente di
elementi. Utilizzate quindi tale conteggio per controllare l’elaborazione.
Per liminare il contenuto della schiera, basterà reimpostare
il contatore a zero.

5. Evitate di utilizzare SORT e %LOOKUP con elementi di
schiera non caricati. Ordinate e cercate solo gli elementi che vi
servono. Utilizzate il contatore visto nel suggerimento precedente
per controllare l’elaborazione:
sorta %subarr( items: 1: count );
idx = %lookup( item.id: items: 1: count );
6. Associate campi descritti esternamente alle schiere. Molti
sistemi legacy salvano dati ripetuti in file. Per esempio, un file
ITEMSALE potrebbe contenere ITEM, YEAR e da SALES01 a
SALES12 per le vendite mensili dell’anno. È possibile associare
campi contigui a schiere per semplificare l’elaborazione:
d itemSaleData e ds extname( itemSale )
d sales s like( sales01 )
d dim( 12 )
d based( salesPtr )
d salesPtr s * inz( %addr( sales01 ) )
La struttura dati ITEMSALEDATA definisce la memoria di lavoro
per un record ITEMSALE. La schiera SALES contiene 12
elementi simili al campo SALES01 in ITEMSALE ed è basata sul
puntatore SALESPTR. SALESPTR viene impostato sull’indirizzo
del sottocampo SALES01 in ITEMSALEDATA. L’effetto è quello
di sovrapporre la schiera SALES ai sottocampi da SALES01 a SALES12.
Cambiando SALES03 verrà così automaticamente aggiornato
anche SALES(3); eliminando il contenuto di SALES(11)
verrà eliminato automaticamente anche il contenuto di SALES11.
7. Ottimizzate i programmi. Utilizzate il parametro OPTIMIZE nei
comandi CRTBNDRPG e CRTRPGMOD. Utilizzando l’ottimizzazione
*BASIC o *FULL migliorano spesso le prestazioni dei programmi
che prevedono un utilizzo intensivo della schiere. •