Robitex's Blog

Ideas in the web

Archivi Mensili: luglio 2010

Riallineare valori testuali


Dati testuali tabellari

A volte abbiamo la necessità di dover riallineare un testo tabellare inserendo o cancellando caratteri spazio. In questo modo i valori delle celle risulteranno allineati in colonne se stampati con un font a spaziatura fissa tipo macchina da scrivere.

Per intenderci, da così:

123 89.08 33.9 5.90 67.5
8 78.00 4944.9 208.93
567 8.99 99.2 90.89 7890.2
41 18.03 5464.6 38.73 993.3

a così:

123 89.08   33.9   5.90   67.5
  8 78.00 4944.9 208.93
567  8.99   99.2  90.89 7890.2
 41 18.03 5464.6  38.73  993.3

È il caso in cui un qualche programma fornisce in output dei file testuali di dati che dobbiamo impaginare per la stampa, oppure, quando è necessario estrarre dati da un file testuale in cui i campi devono essere individuati con la posizione dei caratteri (il cosiddetto tracciato record).

Naturalmente si può fare a mano con un buon editor di testi (che magari facilita il compito permettendo la selezione di aree rettangolari di testo come si puà fare con SciTE), ma se le linee sono migliaia…

Impaginare a mano grandi moli di dati non è solo un compito impossibile e soggetto ad inevitabili errori. In fondo anche le normative sulla sicurezza sul lavoro (come il nostro Testo Unico 81/08) impongono di fare tutto il possibile per evitare compiti ripetitivi e noiosi.

La soluzione

Un programma può svolgere egregiamente questo compito, ed in passato, chissà quante volte ne ho rimpianto la mancanza. Ma adesso che son cresciuto, vi propongo una soluzione grazie a Lua, il linguaggio di scripting più potente della storia!!!

Vai con il codice

Per la parte divertente ci riferiremo dunque a Lua. Lascio a voi il compito eventuale di installare Lua versione 5.1 o successiva sul vostro sistema (su Linux spesso lo trovate già pronto).

#!/usr/bin/lua
-- read the text file line by line
-- and reformatting the spacing about numbers

-- if no output filename is available, one is provided
local filename = arg[1]
local filenameResult = arg[2]

if not arg[2] then
   local i, j, ext = string.find( filename , "%.([^%.]*)$" )
   if ext then
      filenameResult = string.sub(filename, 1, i - 1) .. "_result." .. ext
   else 
      filenameResult = filename .. "_result"
   end
end

local f = assert(io.open( filename , "r" ))
local words = {}
local coldim = {}

while true do
   local line = f:read("*line")
   if line == nil then break end

   j = 0
   k = 1      -- a words counter in a line
   t = {}
   while true do
      i,j = string.find(line, "%S+", j+1)
      if i == nil then break end
      
      -- a word must be processed
      local w = string.sub(line, i, j )
      t[ k ] = w
      local dim = #w
      if coldim[k] == nil then
          coldim[k] = dim
      elseif coldim[k] < dim then
          coldim[k] = dim
      end
      k = k + 1
   end
   words[ #words + 1 ] = t
end

f:close()

local f = assert(io.open( filenameResult ,"w"))

for i,v in ipairs( words ) do
   local rebuildedLine = ''
   for j,w in ipairs( v ) do
      rebuildedLine = rebuildedLine ..
         string.rep(" ", coldim[j]-#w) ..
            ((j>1) and ' ' or '' ) .. w
   end
   f:write(rebuildedLine .. '\n')
end

f:close()
-- end

Il codice si sviluppa dapprima memorizzando nella tabella words le tabelle v contenenti le “parole” (sequenze di caratteri non contenenti spazi). La ricerca delle parole viene risolta utilizzando una funzione della libreria di Lua chiamata string.find che accetta un pattern di ricerca (il pattern “%S” con la s maiuscola, indica un carattere diverso da un blanck, “%S+” indica uno o più caratteri diversi da blanck).

Così elaborato l’intero file, si procede a ricostruire il file risultato ordinatamente linea per linea eseguendo un doppio ciclo for sulla tabella di tabelle. La funzione string.rep (sempre della libreria di Lua), restituisce una stringa ripetendo quella di input per il numero di volte voluto. Quindi è possibile “giustificare” a destra le parole anteponendovi il giusto numero di spazi.

Si ma come si fa a sapere “il giusto numero di spazi”? In un ulteriore tabella memorizzata in coldim viene memorizzato colonna per colonna la massima lunghezza della parola.

Ulteriore notizia riguarda l’operatore # disponibile dalla versione 5.1 di Lua, che restituisce la dimensione dell’oggetto che segue, quindi la lunghezza di una stringa od le dimensioni di un array (una tabella indicizzata con i numeri interi).

Ricordiamoci che in Lua l’unica struttura dati disponibile è la tabella, ovvero un array associativo dove la chiave può essere un numero od una stringa. Per instanziare un oggetto tabella l’unico modo è ricorrere al costrutture che nella forma più semplice è questo {} che crea una tabella vuota e ne restituisce il riferimento che possiamo assegnare ad una variabile.

Se volete è certamente consigliabile studiarsi il libro “Programming in Lua”, citato anche in questo mio post.

Via al codice

Per lanciare lo script salvare il codice in un file con il nome ftab.lua (renderlo eseguibile se nessario), aprire un terminale e digitare il comando:

./ftab.lua nomedelfileditesto
oppure
lua ftab.lua nomedelfileditesto

Bene. Se vi ho risparmiato un mese di lavoro fatemelo sapere!!! Vi manderò il conto.

Ps. naturalmente il codice può essere esteso, per esempio lasciando scegliere all’utente il numero di spazi bianchi tra le colonne del file di output, oppure giustificando “a destra” i numeri ed “a sinistra” le stringhe, od ancora predisporlo per l’elaborazione di file CSV (Comma Separated Value), o per riallineare ambienti tabular in un sorgente LaTeX.
Lua è tremendamente potente.

Metapost: proiezione punto su retta


Potenzialità lineari di Metapost

La sintassi di Metapost è da considerare un po’ datata ed a volte puntigliosa?
Meglio usare programmi di disegno preferibilmente vettoriale?

Oppure possiamo parlare la lingua di Metapost ragionando tranquillamente di relazioni geometriche, semipiani e vettori…

Allora, Metapost parlando, ci proponiamo in questo post di disegnare il punto proiezione di uno dato su un segmento, alla maniera di John Hobby, il creatore originale di questo potente tool basato su Postscript, entrando nei dettagli tecnici.

Un punto qualsiasi

In Metapost per dire che ci stiamo riferendo ad un punto qualsiasi (od anche ad un valore qualsiasi, dipende dal contesto), si usa il termine whatever. Con le informazioni disponibili, Metapost tenterà comunque di individuare un punto esatto (a meno delle approssimazioni numeriche), permettendoci di esprimere facilmente vincoli geometrici.

È anche possibile utilizzare una mediation expression per trovare punti su un segmento. Si premette un numero ai punti tra parentesi quadre, così il punto di mezzo del segmento AB è 0.5[A,B].
Se controllate, abbiamo già fatto uso della mediation in altri post, come per esempio quello riguardante il fiocco di Von Koch, per suddividere il lato in tre parti uguali.

Un punto qualsiasi sulla retta passante per i punti A e B è un’affermazione che si esprime con whatever[A,B].

Rette ortogonali

Che significa in Metapost sottrarre due punti? L’operazione sottrae le coordinate così l’espressione B-A possiamo pensarla come il vettore AB esattamente come se utilizzassimo la notazione comune in Meccanica Razionale, ma con il primo estremo nell’origine.

L’operazione di rotazione di 90° della differenza tra due punti, fornirà quindi il vettore uscente dall’origine e di direzione ortogonale alla retta passante per i due punti. Un breve esempio chiarificatore:

outputtemplate:="ortoa.mps";

beginfig(1);
% draw axis
numeric dim;
dim := 200;
drawarrow origin -- (dim,0); % x
drawarrow origin -- (0,dim); % y

pair a,b;
a := ( 10,32);  % se non sono indicate unità viene
b := (180,80);  % assunto il bp (big point) di Postscript

drawoptions(withcolor red withpen pencircle scaled 2);
drawarrow a--b;
drawarrow origin -- (b-a) dashed evenly;
drawarrow origin -- (b-a) rotated 90 dashed evenly;

% labels
drawoptions(withcolor blue);
dotlabel.bot("A",a);
dotlabel.bot("B",b);

label.bot(btex $x$ etex,(dim,0));
label.lft(btex $y$ etex,(0,dim));
label.llft(btex $O$ etex,origin);
endfig;
end;
A Metapost example on vector subtraction and rotation

A Metapost example on vector subtraction and rotation

Proiezione ortogonale (potenza di Metapost)

Chiamiamo C il punto da proiettare e P il punto di proiezione sulla retta AB.
Allora, se si vuole che il vettore di proiezione CP sia proporzionale al vettore della retta di proiezione scriveremo:
p-c = whatever*(b-a) rotated 90;
Nell’espressione whatever assume il ruolo di un fattore per condizione geometrica di parallelismo dei vettori.

Aggiungiamo il secondo vincolo, ovvero che il punto P appartiene alla retta AB:
p = whatever[a,b];

Adesso Metapost è in grado di risolvere il sistema di equazioni per trovare il punto P.

Codice completo e compilabile

Ricordo che l’output Metapost è direttamente utilizzabile da pdfLaTeX, quindi è possibile includerlo in documento per ottenere il pdf. Ecco quindi il codice immediatamente compilabile, dove la distanza di P dalla retta viene calcolata con la distanza pitagorica implementata in Metapost con l’operatore ++, e stampata sul terminale con il comando show:

outputtemplate:="ortob.mps";

beginfig(1);
% draw axis
numeric dim;
dim := 200;
drawarrow origin -- (dim,0); % x
drawarrow origin -- (0,dim); % y

pair a,b,c,p;
a := ( 25,32);  % se non sono indicate unità viene
b := (180,80);  % assunto il bp (big point) di Postscript
c := (80,180);

p-c = whatever * (b-a) rotated 90;
p = whatever[a,b];

drawoptions(withcolor red withpen pencircle scaled 2);
draw a--b;
draw p--c dashed evenly;

% labels
labeloffset := 6;
drawoptions(withcolor blue);
dotlabel.bot("A",a);
dotlabel.bot("B",b);
dotlabel.top("C",c);
dotlabel.bot("P",p);

label.llft(btex $x$ etex,(dim,0));
label.llft(btex $y$ etex,(0,dim));
label.llft(btex $O$ etex,origin);

numeric dist;
dist := xpart (c - p) ++ ypart (c - p);
show dist;%   -> 125.10623 bp
endfig;
end;
Metapost figure on point projection

Metapost figure on point projection

Saluti e buona estate!

%d blogger hanno fatto clic su Mi Piace per questo: