Visualizza il feed RSS

Titolo provvisorio...

LUT? Sì, grazie... (e due)

Valuta questo inserimento
di pubblicato il 13-06-2011 alle 03:19 (3253 Visite)
Come promesso nella scorsa puntata, vediamo brevemente la creazione delle LUT correlate per la soluzione tabulare del semplice problemino didattico inerente la tavola periodica degli elementi.

Invito tutti i miei lettori a considerare che, dopo un apprendistato più o meno lungo su una data piattaforma, il programmatore (embedded) quadratico medio si troverà già pronto sul disco, ad ogni nuovo progetto di media complessità, fino ad un buon 40-60% del codice C e Assembly necessario per la famiglia target.
Al contrario, il tempo mediamente necessario per la generazione e convalida di tabelle e array pare rimanere pressoché invariato... ne consegue che buona parte del tempo dedicato allo sviluppo s'invola in questi task "secondari", specialmente nella primissima parte della carriera di un imberbe programmatore.

Da qui scaturisce la sana abitudine di automatizzare al massimo questi compiti e la loro verifica, impiegando al meglio linguaggi di scripting e ambienti dedicati.

Naturalmente sarebbe possibile digitare direttamente nei propri sorgenti tutte le LUT, da bravi monaci amanuensi cluniacensi e cistercensi, apportando anche correzioni manuali in caso di modifiche. Tuttavia, per chi non è affetto da manie algofile autopunitive o versato in pratiche masochistiche BDSM varie, la soluzione più naturale e razionale consiste nell'utilizzare a piene mani strumenti di automazione ed elaborazione di questi dati, partendo da formati di interscambio semplici e facilmente controllabili.

Il file di dati dal quale partire può essere digitato manualmente, con ovvio rischio di errori e crisi catatonico-narcolettiche, oppure furbescamente esportato da qualche semplice applicativo scolastico o dal solito spreadsheet reperito presso un qualsiasi presidio didattico online.

L'importante è preparare un file CDF che, a meno di estrose idiosincrasie personali nella scelta dei separatori, somigli il più possibile al seguente tracciato:
codice:
"H";"Idrogeno"
"He";"Elio"
"Li";"Litio"
"Be";"Berillio"
...
Siete inoltre vivamente pregati di esaminare la congruenza dei dati immessi e l'assenza di errori.

Gli elementi sono qui, con ogni evidenza, implicitamente ordinati per numero atomico crescente.

Il risultato al quale intendiamo giungere potrebbe somigliare al seguente schema:
codice:
#ifndef __PTABLE_H__
#define __PTABLE_H__

#define LUT_ROWS     27
#define LUT_COLS     27
#define NUM_ELEMS   118

const char ElemLookUp[LUT_ROWS][LUT_COLS] = {
/*         -    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o    p    q    r    s    t    u    v    w    x    y    z */
/* A */ {  0,   0,   0,  89,   0,   0,   0,  47,   0,   0,   0,   0,  13,  95,   0,   0,   0,   0,  18,  33,  85,  79,   0,   0,   0,   0,   0}
/* B */ {  5,  56,   0,   0,   0,   4,   0,   0, 107,  83,   0,  97,   0,   0,   0,   0,   0,   0,  35,   0,   0,   0,   0,   0,   0,   0,   0}
...
/* Z */ {  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  30,   0,   0,   0,  40,   0,   0,   0,   0,   0,   0,   0,   0}
/* - */ {  0,   0, 112,   0,   0,   0,   0,   0, 116,   0,   0,   0,   0,   0,   0, 118, 115, 114,   0, 117, 113,   0,   0,   0,   0,   0,   0}};

const char *Elements[NUM_ELEMS] = {
    "Unknown"      ,
    "Hydrogen"     ,    "Helium"       ,    "Lithium"      ,    "Beryllium"    ,    "Boron"        ,
...
    "Roentgenium"  ,    "Ununbium"     ,    "Ununtrium"    ,    "Ununquadium"  ,    "Ununpentium"  ,
    "Ununhexium"   ,    "Ununseptium"  };

#endif

/* EOF: "p_table.h" */
Il metodo migliore e più sbrigativo per elaborare questi file di testo consiste nell'uso di linguaggi dedicati, come l'ottimo ICON, del quale abbiamo già trattato in passato. La stesura del relativo programma, decisamente banale, è qui lasciata come semplice esercizio per il lettore interessato.

Dal punto di vista formativo è invece più utile analizzare soluzioni più generali e meno concise. AWK è un ottimo linguaggio-scuola in questo tipo di applicazioni, e questa è la soluzione proposta, ampiamente commentata e aperta a migliorie di ogni sorta (prima tra tutte, un bel controllo di congruenza sul numero di elementi chimici effettivamente allocati nella LUT), che costituiscono un ottimo esercizio.

codice:
#!/bin/awk

##
## ptable.awk
##
## Il file da leggere ha il seguente semplicissimo formato:
## $1  $2
## "H";"Idrogeno"
## "He";"Elio"
## "Li";"Litio"
##

## Implementazione casalinga della ASC()
## portabile su tutti i flavour di awk
function ord(c)
{
  return ASCIImap[c]
}

BEGIN {
    ## Costanti di progetto
    LUT_ROWS  = 27;
    LUT_COLS  = 27;
    NUM_ELEMS = 118;
    
    # Si imposta il separatore dei campi per il CDF
    FS = ";"

    # Implementazione casalinga della ASC()
    for(i = 0; i < 256; i++) 
    {
        ASCIImap[sprintf("%c", i)] = i
    }
    
    # Inizializzazione delle tabelle di lookup
    for (i = 0; i < LUT_ROWS; i++)
    {
        for (j = 0; j < LUT_COLS; j++)
        {
            LUT[i,j] = 0;
        }
    }
    
    Elems[0] = "\"Unknown\"";
}

{
    ## Eliminazione delle virgolette dal primo campo
    gsub(/"/, "", $1);
    
    # Inizializzazione degli indici
    r = 0;
    c = 0;

    # Mappatura del simbolo in indici.
    # Si assume che il file di dati sia stato     
    # preparato da esseri senzienti...
    if (length($1) == 1)
    {
        r = ord(toupper(substr($1, 1, 1))) - 65;
        c = 0;
    }
    else
    if (length($1) == 2)
    {
        r = ord(toupper(substr($1, 1, 1))) -65;
        c = ord(toupper(substr($1, 2, 1))) -64;
    }
    else
    if (length($1) == 3)
    {
        r = LUT_ROWS -1;
        c = ord(toupper(substr($1, 3, 1))) -64;
    }
    
    # Se la vostra versione preferita di AWK non supporta FNR,
    # decisamente e' giunto il momento di cambiarla...
    LUT[r,c] = FNR;
    Elems[FNR] = $2;
}

END {
    ## Creazione dello header di output.
    ## La notazione "batch-alike" usata da awk non e' felicissima,
    ## ma risulta banalmente gestibile con qualsiasi editor 
    ## che supporti la modalità colonna.
    ## Lo sviluppatore avvezzo a windows non ama 
    ## e non si attende l'output diretto per default su stdio.
    
    # Sarebbe d'uopo controllare qui che FNR sia pari a NUM_ELEMS...

    print "#ifndef __PTABLE_H__\n#define __PTABLE_H__\n"      >"p_table.h";
    printf "#define LUT_ROWS    %3d\n", LUT_ROWS             >>"p_table.h";
    printf "#define LUT_COLS    %3d\n", LUT_COLS             >>"p_table.h";
    printf "#define NUM_ELEMS   %3d\n\n", NUM_ELEMS          >>"p_table.h";
    printf "const char ElemLookUp[LUT_ROWS][LUT_COLS] = {\n" >>"p_table.h";
    
    printf "/*         -    a    b    c    d    e    f"\
           "    g    h    i    j    k    l    m    n"\
           "    o    p    q    r    s    t    u    v"\
           "    w    x    y    z */"                         >>"p_table.h";

    for (i = 0; i < LUT_ROWS; i++)
    {
        if (LUT_ROWS -1 != i)
        {
            printf "\n/* %c */ {", i + 65                    >>"p_table.h";
        }
        else
        {
            printf "\n/* - */ {"                             >>"p_table.h";
        }
        
        for (j = 0; j < LUT_COLS -1; j++)
        {
            printf "%3d, ", LUT[i,j]                         >>"p_table.h";
        }
        printf "%3d}", LUT[i,j]                              >>"p_table.h";
    }
    
    printf "};\n\nconst char *Elements[NUM_ELEMS] = {"     >>"p_table.h";
    for (i = 0; i < NUM_ELEMS -1; i++)
    {
        if (0 == i % 5)
        {
            printf "\n   "                                         >>"p_table.h";
        }
        printf " %-15s,", Elems[i]                        >>"p_table.h";
    }
    printf " %-15s};\n", Elems[i]                         >>"p_table.h";
    print "\n#endif\n\n/* EOF: \"p_table.h\" */"             >>"p_table.h";
}
## /* EOF: ptable.awk */
L'implementazione è stata collaudata sul file di dati usando i tre più diffusi interpreti AWK per ambiente Windows (XP SP3): gawk, nawk e l'ottimo mawk.

Naturalmente è possibile usare linguaggi più tradizionali, ad esempio il C, per generare le LUT necessarie. Non si deve però dimenticare che generatori di codice siffatti sono tipicamente programmi usa-e-getta, pensati per un lavoro sporco e veloce su pochi dati preparati direttamente da chi dovrà elaborarli, e comunque facilmente controllabili a vista: dunque il più tipico dominio applicativo per linguaggi di scripting e dedicati, che non richiedono di metter mano al compilatore.

L'implementazione C proposta vuole, al solito, superare gli angusti confini del problemino preso a spunto per questa entry: vi è presente infatti un abbozzo di infrastruttura per la gestione degli errori, che - pur non gestendo ogni possibile caso e rinunciando a priori alla totale robustezza di un vero character processor, per evitare appesantimenti del tutto ingiustificati - confida un po' meno ciecamente dello script AWK nella bontà dei dati contenuti nel file CDF. Ciò costituirà uno spunto importante per tutti gli studenti, che sono anzi invitati a completare ed arricchire tale sezione, gestendo esplicitamente i rami non produttivi dell'albero computazionale costituito dalle strutture di controllo annidate nell'inner loop della funzione di input processing.

Il numero complessivo di LOC risulta all'incirca doppio rispetto allo script AWK proposto sopra.
Si noti anche che lo header prodotto risulta intenzionalmente meno "abbellito" rispetto a quanto creato dal summenzionato script per AWK. Ne risulta un ulteriore buon esercizio lasciato al lettore volenteroso.

codice:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define BUFF_SIZE      128
#define MAX_SYM_LEN      3

#define NUM_ELEMENTS   118
#define LUT_ROWS        27
#define LUT_COLS        27

#define str(x) # x
#define xstr(x) str(x)

typedef enum {FALSE, TRUE} boole_t;

typedef enum {
    ERR_NONE,
    ERR_FILE_OPEN,
    ERR_FILE_CREATE,
    ERR_BAD_SYM,
    ERR_MISSING_NAME,
    ERR_MISSING_DATA
    } error_t;

#define NUM_ERRORS ERR_MISSING_DATA +1

typedef struct {
    const char *fname;
    int   fline;
    } err_parms_t;

error_t LastError;

char Sym[MAX_SYM_LEN +1];
char LookUp[LUT_ROWS][LUT_COLS];
char *Elements[NUM_ELEMENTS +1];

char LBuff[BUFF_SIZE];

void err_msg(err_parms_t *ep)
{
    const char *errors[] = {
        "# Errore nell'apertura del file di input \"%s\"!\n\n",
        "# Errore nella creazione del file di output \"%s\"!\n\n",
        "# Errore di congruenza nel file di input \"%s\" alla linea %d:\n"
          "# Il simbolo contiene un carattere non alfabetico!\n\n",
        "# Errore durante la lettura dei dati dal file \"%s\":\n"
          "# Linee attese " xstr(NUM_ELEMENTS) ", linee lette %d!\n\n",
        "# Errore durante la lettura dei dati dal file \"%s\":\n"
          "# Elementi attesi " xstr(NUM_ELEMENTS) ", elementi assegnati %d!\n\n"
    };

    if (LastError < NUM_ERRORS)
    {
        printf(errors[LastError -1], ep->fname, ep->fline);
    }
}

boole_t read_file(const char *fn)
{
    FILE *fp_in;
    err_parms_t ep;
    const char Delim[] = "\";\r\n";

    int Line = 0;
    int tot_elements = 0;

    ep.fname = fn;
    ep.fline = 0;

    fp_in = fopen(fn, "r");
    if (NULL == fp_in)
    {
        LastError = ERR_FILE_OPEN;
        err_msg(&ep);
        return (FALSE);
    }

    printf("> Lettura del file di input \"%s\"\n", fn);

    while (NULL != fgets(LBuff, BUFF_SIZE, fp_in))
    {
        if (0 < strlen(LBuff))
        {
            char *p;

            ++Line;
            ep.fline = Line;

            p = strtok(LBuff, Delim);
            if (NULL != p)
            {
                int le;
                if (!isalpha(*p))
                {
                    LastError = ERR_BAD_SYM;
                    err_msg(&ep);
                    break;
                }

                strcpy(Sym, p);
                le = strlen(Sym);
                if (1 == le)
                {
                    Sym[1] = '@';
                    Sym[2] = '\0';
                    le = 2;
                }

                p = strtok(NULL, Delim);
                if (NULL != p)
                {
                    Elements[Line] = strdup(p);

                    if (2 == le)
                    {
                        char i, j;

                        if (!isalpha(Sym[1]) && '@' != Sym[1])
                        {
                            LastError = ERR_BAD_SYM;
                            err_msg(&ep);
                            break;
                        }

                        i = (char)(toupper(Sym[0]) - 'A');
                        j = (char)(toupper(Sym[1]) - '@');

                        LookUp[i][j] = (char)Line;
                        tot_elements++;
                    }

                    if (3 == le)
                    {
                        char j;
                        /* Controllo formale a cura del lettore... */
                        j = (char)(toupper(Sym[2]) - '@');
                        LookUp[LUT_ROWS -1][j] = (char)Line;
                        tot_elements++;
                    }
                }
                /* else ... (caso elemento mancante) */ 
            }
        }
    }

    fclose(fp_in);

    if (ERR_NONE == LastError)
    {
        if (NUM_ELEMENTS != Line)
        {
            LastError = ERR_MISSING_DATA;
            err_msg(&ep);
            return (FALSE);
        }

        if (NUM_ELEMENTS != tot_elements)
        {
            LastError = ERR_MISSING_DATA;
            ep.fline = tot_elements;
            err_msg(&ep);
        }

        return (boole_t)(ERR_NONE == LastError);
    }
    else
    {
        return(FALSE);
    }
}

void create_file(const char *fn)
{
    FILE *fp_out;
    int i, j;
    err_parms_t ep;

    ep.fname = fn;
    ep.fline = 0;

    fp_out = fopen(fn, "w");
    if (NULL == fp_out)
    {
        LastError = ERR_FILE_CREATE;
        err_msg(&ep);
        return;
    }

    printf("> Creazione del file di output \"%s\"\n", fn);

    fprintf(fp_out, "#ifndef __PTABLE_H__\n#define __PTABLE_H__\n\n"
            "#define LUT_ROWS    " xstr(LUT_ROWS) "\n"
            "#define LUT_COLS    " xstr(LUT_ROWS) "\n\n"
            "const char ElemLookUp[LUT_ROWS][LUT_COLS] = {\n");

    for (i = 0; i < LUT_ROWS; ++i)
    {
        strcpy(LBuff, "    ");
        for (j = 0; j < LUT_COLS; ++j)
        {
            sprintf(LBuff + 5 * j + 4, "%c%4d",
                    (0 == j ? '{' : ','), LookUp[i][j]);
        }
        if (LUT_ROWS -1 != i)
        {
            strcat(LBuff, "},\n");
        }
        else
        {
            strcat(LBuff, "}};\n\n");
        }

        fputs(LBuff, fp_out);
    }

    fprintf(fp_out, "\nconst char *Elements[] = {\n");
    for (i = 0; i < NUM_ELEMENTS; ++i)
    {
        sprintf(LBuff, "    \"%s\"%s\n",
                Elements[i],
                (NUM_ELEMENTS -1 == i) ? "};\n" : ",");
        fputs(LBuff, fp_out);
    }
    fprintf(fp_out, "\n#endif\n\n/* EOF: %s */", fn);

    fclose(fp_out);

    return;
}


int main(void)
{
    const char F_in[]  = "ptable.txt";
    const char F_out[] = "ptable.h";
    boole_t res;
    int i, j;

    LastError = ERR_NONE;

    Elements[0] = "Unknown";

    for (i = 0; i < LUT_ROWS; ++i)
    {
        for (j = 0; j < LUT_COLS; ++j)
        {
            LookUp[i][j] = 0;
        }
    }

    res = read_file(F_in);

    if (FALSE == res)
    {
        return(LastError);
    }

    create_file(F_out);

    return(LastError);
}
/* EOF: cr8ptable.c */
Come d'abitudine, sono presenti due allegati che riportano i sorgenti qui proposti:
cr8ptable_c.txt,
ptable_awk.txt

aggiornamento da 04-12-2015 a 01:11 di M.A.W. 1968

Categorie
Programmazione

Commenti