Successivo: , Precedente: , Su: Funzioni   [Contenuti][Indice]


9.3 Chiamate indirette di funzione

Questa sezione descrive un’estensione avanzata, specifica di gawk.

Spesso può essere utile ritardare la scelta della funzione da chiamare fino al momento in cui il programma viene eseguito. Per esempio, potrebbero esserci diversi tipi di record in input, ciascuno dei quali dovrebbe essere elaborato in maniera differente.

Solitamente, si userebbe una serie di istruzioni if-else per decidere quale funzione chiamare. Usando la chiamata indiretta a una funzione, si può assegnare il nome della funzione da chiamare a una variabile di tipo stringa, e usarla per chiamare la funzione. Vediamo un esempio.

Si supponga di avere un file con i punteggi ottenuti negli esami per i corsi che si stanno seguendo, e che si desideri ottenere la somma e la media dei punteggi ottenuti. Il primo campo è il nome del corso. I campi seguenti sono i nomi delle funzioni da chiamare per elaborare i dati, fino a un campo “separatore” ‘dati:’. Dopo il separatore, fino alla fine del record, ci sono i vari risultati numerici di ogni test.

Ecco il file iniziale:

Biologia_101 somma media dati: 87.0 92.4 78.5 94.9
Chimica_305 somma media dati: 75.2 98.3 94.7 88.2
Inglese_401 somma media dati: 100.0 95.6 87.1 93.4

Per elaborare i dati, si potrebbe iniziare a scrivere:

{
    corso = $1
    for (i = 2; $i != "dati:"; i++) {
        if ($i == "somma")
            somma()   # elabora l'intero record
        else if ($i == "media")
            media()
        …           # e così via
    }
}

Questo stile di programmazione funziona, ma può essere scomodo. Con la chiamata indiretta di funzione, si può richiedere a gawk di usare il valore di una variabile come nome della funzione da chiamare.

La sintassi è simile a quella di una normale chiamata di funzione: un identificativo, seguito immediatamente da una parentesi aperta, qualche argomento, e una parentesi chiusa, con l’aggiunta di un carattere ‘@’ all’inizio:

quale_funzione = "somma"
risultato = @quale_funzione()   # chiamata della funzione somma()

Ecco un intero programma che elabora i dati mostrati sopra, usando la chiamata indiretta di funzioni:

# chiamataindiretta.awk --- esempio di chiamata indiretta di funzioni

# media --- calcola la media dei valori dei campi $primo - $ultimo

function media(primo, ultimo,   somma, i)
{
    somma = 0;
    for (i = primo; i <= ultimo; i++)
        somma += $i

    return somma / (ultimo - primo + 1)
}

# somma --- restituisce la somma dei valori dei campi $primo - $ultimo

function somma(primo, ultimo,   totale, i)
{
    max = 0;
    for (i = primo; i <= ultimo; i++)
        totale += $i

    return totale
}

Queste due funzioni presuppongono che si lavori con dei campi; quindi, i parametri primo e ultimo indicano da quale campo iniziare e fino a quale arrivare. Per il resto, eseguono i calcoli richiesti, che sono i soliti:

# Per ogni record,
# stampa il nome del corso e le statistiche richieste
{
    nome_corso = $1
    gsub(/_/, " ", nome_corso)  # Rimpiazza _ con spazi

    # trova campo da cui iniziare
    for (i = 1; i <= NF; i++) {
        if ($i == "dati:") {
            inizio = i + 1
            break
        }
    }

    printf("%s:\n", nome_corso)
    for (i = 2; $i != "dati:"; i++) {
        quale_funzione = $i
        printf("\t%s: <%s>\n", $i, @quale_funzione(inizio, NF) "")
    }
    print ""
}

Questo è il ciclo principale eseguito per ogni record. Stampa il nome del corso (con le lineette basse sostituite da spazi). Trova poi l’inizio dei dati veri e propri, salvandolo in inizio. L’ultima parte del codice esegue un ciclo per ogni nome di funzione (da $2 fino al separatore, ‘dati:’), chiamando la funzione il cui nome è specificato nel campo. La chiamata di funzione indiretta compare come parametro nella chiamata a printf. (La stringa di formattazione di printf usa ‘%s’ come specificatore di formato, affinché sia possibile usare funzioni che restituiscano sia stringhe che numeri. Si noti che il risultato della chiamata indiretta è concatenato con la stringa nulla, in modo da farlo considerare un valore di tipo stringa).

Ecco il risultato dell’esecuzione del programma:

$ gawk -f chiamataindiretta.awk dati_dei_corsi
-| Biologia 101:
-|     somma: <352.8>
-|     media: <88.2>
-|
-| Chimica 305:
-|     somma: <356.4>
-|     media: <89.1>
-|
-| Inglese 401:
-|     somma: <376.1>
-|     media: <94.025>

La possibilità di usare la chiamata indiretta di funzioni è più potente di quel che si possa pensare inizialmente. I linguaggi C e C++ forniscono “puntatori di funzione” che sono un metodo per chiamare una funzione scelta al momento dell’esecuzione. Uno dei più noti usi di questa funzionalità è la funzione C qsort(), che ordina un vettore usando il famoso algoritmo noto come “quicksort” (si veda l’articolo di Wikipedia per ulteriori informazioni). Per usare questa funzione, si specifica un puntatore a una funzione di confronto. Questo meccanismo consente di ordinare dei dati arbitrari in una maniera arbitraria.

Si può fare qualcosa di simile usando gawk, così:

# quicksort.awk --- Algoritmo di quicksort, con funzione di confronto
#                   fornita dall'utente

# quicksort --- Algoritmo di quicksort di C.A.R. Hoare.
#               Si veda Wikipedia o quasi ogni libro
#               che tratta di algoritmi o di informatica.

function quicksort(dati, sinistra, destra, minore_di,    i, ultimo)
{
    if (sinistra >= destra)  # non fa nulla se il vettore contiene
        return               # meno di due elementi

    quicksort_scambia(dati, sinistra, int((sinistra + destra) / 2))
    ultimo = sinistra
    for (i = sinistra + 1; i <= destra; i++)
        if (@minore_di(dati[i], dati[sinistra]))
            quicksort_scambia(dati, ++ultimo, i)
    quicksort_scambia(dati, sinistra, ultimo)
    quicksort(dati, sinistra, ultimo - 1, minore_di)
    quicksort(dati, ultimo + 1, destra, minore_di)
}

# quicksort_scambia --- funzione ausiliaria per quicksort,
#                       sarebbe meglio fosse nel programma principale

function quicksort_scambia(dati, i, j,      salva)
{
    salva = dati[i]
    dati[i] = dati[j]
    dati[j] = salva
}

La funzione quicksort() riceve il vettore dati, gli indici iniziali e finali da ordinare (sinistra e destra), e il nome di una funzione che esegue un confronto “minore di”. Viene quindi eseguito l’algoritmo di quicksort.

Per fare uso della funzione di ordinamento, torniamo all’esempio precedente. La prima cosa da fare è di scrivere qualche funzione di confronto:

# num_min --- confronto numerico per minore di

function num_min(sinistra, destra)
{
    return ((sinistra + 0) < (destra + 0))
}

# num_magg_o_ug --- confronto numerico per maggiore o uguale

function num_magg_o_ug(sinistra, destra)
{
    return ((sinistra + 0) >= (destra + 0))
}

La funzione num_magg_o_ug() serve per ottenere un ordinamento decrescente (dal numero più alto al più basso); quando è usato per eseguire un test per “minore di”, in realtà fa l’opposto (maggiore o uguale a), il che conduce a ottenere dati ordinati in ordine decrescente.

Poi serve una funzione di ordinamento. Come parametri ha i numeri del campo iniziale e di quello finale, e il nome della funzione di confronto. Costruisce un vettore con i dati e richiama appropriatamente quicksort(); quindi formatta i risultati mettendoli in un’unica stringa:

# ordina --- ordina i dati a seconda di `confronta'
#             e li restituisce come un'unica stringa

function ordina(primo, ultimo, confronta,      dati, i, risultato)
{
    delete dati
    for (i = 1; primo <= ultimo; primo++) {
        dati[i] = $primo
        i++
    }

    quicksort(dati, 1, i-1, confronta)

    risultato = dati[1]
    for (i = 2; i in dati; i++)
        risultato = risultato " " dati[i]

    return risultato
}

Per finire, le due funzioni di ordinamento chiamano la funzione ordina(), passandole i nomi delle due funzioni di confronto:

# ascendente --- ordina i dati in ordine crescente
#            e li restituisce sotto forma di stringa

function ascendente(primo, ultimo)
{
    return ordina(primo, ultimo, "num_min")
}

# discendente --- ordina i dati in ordine decrescente
#            e li restituisce sotto forma di stringa

function discendente(primo, ultimo)
{
    return ordina(primo, ultimo, "num_magg_o_ug")
}

Ecco una versione estesa del file-dati:

Biologia_101 somma media ordina discendente dati: 87.0 92.4 78.5 94.9
Chimica_305 somma media ordina discendente dati: 75.2 98.3 94.7 88.2
Inglese_401 somma media ordina discendente dati: 100.0 95.6 87.1 93.4

Per finire, questi sono i risultati quando si esegue il programma in questa versione migliorata:

$ gawk -f quicksort.awk -f indirettacall.awk class_data2
-| Biologia 101:
-|     somma: <352.8>
-|     media: <88.2>
-|     ascendente: <78.5 87.0 92.4 94.9>
-|     discendente: <94.9 92.4 87.0 78.5>
-|
-| Chimica 305:
-|     somma: <356.4>
-|     media: <89.1>
-|     ascendente: <75.2 88.2 94.7 98.3>
-|     discendente: <98.3 94.7 88.2 75.2>
-|
-| Inglese 401:
-|     somma: <376.1>
-|     media: <94.025>
-|     ascendente: <87.1 93.4 95.6 100.0>
-|     discendente: <100.0 95.6 93.4 87.1>

Un altro esempio in cui le chiamate indirette di funzione sono utili è costituito dall’elaborazione di vettori. La descrizione si può trovare Attraversare vettori di vettori.

Occorre ricordarsi di anteporre il carattere ‘@’ prima di una chiamata indiretta di funzione.

A partire dalla versione 4.1.2 di gawk, le chiamate indirette di funzione possono anche essere usate per chiamare funzioni predefinite e con funzioni di estensione (vedi la sezione Scrivere estensioni per gawk). Ci sono alcune limitazioni nel richiamare in maniera indiretta delle funzioni predefinite, come qui dettagliato:

gawk fa del suo meglio per rendere efficiente la chiamata indiretta di funzioni. Per esempio, nel ciclo seguente:

for (i = 1; i <= n; i++)
    @quale_funzione()

gawk ricerca solo una volta quale funzione chiamare.


Note a piè di pagina

(65)

Questa limitazione potrebbe cambiare in una futura versione; per appurarlo, si controlli la documentazione che accompagna la versione in uso di gawk.


Successivo: , Precedente: , Su: Funzioni   [Contenuti][Indice]