Archivi Categorie: Linguaggi

Linguaggi di programmazione

Gestire la clipboard

top4

A me nessuno dice mai niente ma indagando ontehtoobz ogni tanto trovo cose…
Come xclip, fa proprio quello che volevo da tanto tempo. A dirla tutta ne ho già parlato ma non in modo organico, cerco di rimediare.

Niente di meglio che un esempio per vedere cosa fa, con la teoria non sono tanto buono, ecco:

c0

e nella clipboard ho, vediamo con Crtl-V

prima riga
seconda
terza.

cioè l’output del comando precedente. Comodo, molto più comodo di selezionare il testo e copiarlo con Maiusc-Ctrl-C.

Tanto comodo (come si vedrà in un prossimo post (forse)) che ho creato lo script _k, abilitato e messo in ~/bin

#!/bin/bash
xclip -f -selection clipboard

Da testare:

c2

e nella clipboard ho:

3.142857142857143
3.141592653589793

Come Ctrl-V fa coppia con Ctrl-C creo _p che fa coppia di _k:

#!/bin/bash
xclip -o  -selection clipboard

lo abilito e sposto in ~/bin ed ecco:

c4

Per sapere tutto di xclip aprire questo link. Prossimamente risulterà meno oscuro come intendo utilizzare _k &co (probabilmente… forse…) 😐

La precisione e la realtà

s-p

Ogni tanto qualcuno riporta una variante di come il ‘puter sbaglia clamorosamente su cose semplicissimissime. Per esempio ecco Vanessa (rockz! 💥, vorei essere come lei (anche se non sono invidioso per natura, nèh!)): […] the people that make fun of JS for 0.1 + 0.2 ≠ 0.3.

Uh! a me fa venire in mente due cose. Comincio con la verifica

$ python3 -q
>>> 0.1 + 0.2
0.30000000000000004
>>>
$ node
> 0.1 + 0.2
0.30000000000000004
$ racket
Welcome to Racket v6.11.
> (+ 0.1 0.2)
0.30000000000000004
> ;; ma anche
  (+ 1/10 2/10)
3/10
> ;; e
  (exact->inexact (+ 1/10 2/10))
0.3
$ echo "0.1 + 0.2" | bc
.3
$ calc 0.1 + 0.2
	0.3
$ maxima -q
(%i1) 0.1 + 0.2;
(%o1)                                 0.3

OK, Racket rockz! 💥, come già si sapeva. Ma gli interpretati dinamicamente come JavaScript, Python e altri ancora, da me non usati correntemente, fanno proprio come dice Vanessa (et al.).

Non è solo una stravaganza della REPL, è davvero vero:

$ python3 -q
>>> (0.1 + 0.2) == 0.3
False
>>> round((0.1 + 0.2), 3) == 0.3
True

Non è una cosa nuova, anzi –qui entro nel c’era una volta– io che sono vecchio ricordo che fin da quando ho cominciato c’erano due funzioni (nostre, cioè fatte da uno di noi o prese da qualche guru nostrano (o anche no)): LEQR e LEQD, rispettivamente per argomenti REAL (4 bytes) e DOUBLE PRECISION (8 bytes); da dichiarare LOGICAL. Con Python diventano una sola, qeq (sta per quasi-equal):

def qeq(a, b):
    return abs(a - b) < 1e-15

Da provare:

$ py3
>>> def qeq(a, b):
...     return abs(a - b) < 1e-15 
... 
>>> qeq(0.1 + 0.2, 0.3)
True
>>> from math import pi
>>> qeq(22/7, pi)
False
>>> #difatti
>>> 22/7 - pi
0.0012644892673496777

Ma se devo dire tutta la verità questa funzione (nelle due varianti) non veniva usata: era lunga una sola riga quindi niente risparmio di battute per contro l’uso comportava un carico maggiore per il sistema, la chiamata a funzione. Volendo potrei raccontare dello statement function, ecco l’inizio di pag. 7-7 del manuale del Fortran IV di Pr1me:

st-fun

Anch’io so cose lollose come Vanessa, solo di utilità assolutamente nulla (oggi, come passa il tempo!).

H/T a AP che mi ha corretto la bozza. E i suggerimenti.

Numeri troppo grandi o troppo piccoli – 1

Scrooge-McDuck-Money-Bin-1000x562

Alle volte capita di avere a che fare con numeri che sono non come quelli normali, immaginabili facilmente dagli umani (anche da me).

Escluderei subito Paperon de’ Paperoni che, secondo Don Rosa, possiede five multiplujillion, nine impossibidillion, seven fantasticatrillion dollars and sixteen cents e per Carl Banks il deposito (Bin) contiene three cubic acres of money e qui non è solo questione di ordini di grandezza ma peggio, roba che in confronto gli stringhisti sono sensati, per cui salto e ricomincio.

Prendiamo per esempio gli astronomi, loro trattano roba come:

Sono parenti stretti dei fisici, a occhio nudo non si riesce (almeno io non ne sono capace) a distinguere gli uni dagli altri. Ma anche loro hanno (danno) numeri che –per esempio:

  • Massa del protone: 1.67262171(29)e-27 kg ossia 0.938 GeV/c2;
  • L’elettrone è ancora più piccolo (ma siamo lì) invece il neutrino (uno a caso, non facciamo i difficili) andiamo su 0.05 eV/c2 o 8.913309e-38 kg.

 Senza senso  Per fortuna esistono i multipli e i sottomultipli delle unità di misura, almeno per SI, il Sistema internazionale di unità di misura.

Quindi usare SI, unità e multipli; poi quando servono se ne fanno altre, come già visto per il parsec. Per esempio i fisici usano il barn (b = 10e-28 m2). Umorismo qui: un fienile di quelle dimensioni non ha senso (tranne per i fisici, ovviamente, Marco lo usa a volte, anche inverso).

Senza arrivare a questi estremi anch’io (con l’aiuto di più amici, questi script sono nati come un gioco, facendo altro) sto tentando di addomesticarli per quanto possibile. Il lavoro non è concluso ma un paio di script consentono di visualizzare il numero normale in formato scientifico (2sc) e viceversa (2ns).

Rendere il numero in formato scientifico – 2sc

#!/bin/bash
N=$@
if [ -z $NFORM ]; then
    NFORM="%.4e"
fi
N=$(echo $N | sed 's/\./\,/g')
N=$(printf "$NFORM  " $N)
N=$(echo $N | sed 's/\,/\./g')
echo $N | xclip -f -selection clipboard

Il lavoro viene svolto da printf, on base al formato, %.ne dove n è un intero positivo non troppo grande; di deafault vale 4, cioè quattro cifre decimali ma è possibile cambiarlo facilmente senza modificare lo script (come vedremo tra breve).

C’è un problema, per alcuni di noi, quelli abituati a programmare: Bash vuole la virgola come simbolo di definizione per la parte decimale (per noi italiani) contrariamente all’abitudine e a (quasi) tutti i linguaggi di programmazione. Niente panico con sed si risolve prima di subito.

Visto che il risultato di queste trasformazioni è spesso da usare immediatamente entra in gioco xclip che inserisce nella clipboard il risultato visualizzato.

$ 2sc 9460700000000000
9.4607e+15
$ 2sc 30857000000000000
3.0857e+16
$ 2sc 149597870700
1.4960e+11

e, ovviamente

$ 2sc 9460700000000000 30857000000000000 49597870700
9.4607e+15 3.0857e+16 4.9598e+10

Il numero di cifre decimali è facilmente ridefinibile: se non va bene il alore 4 di default si può ridefinire la variabile d’ambiente NFORM, con

export NFORM='%.3e'

e si può verificare con

$ echo $NFORM
%.3e

e si avrà

$ 2sc 9460700000000000 30857000000000000 49597870700
9.461e+15 3.086e+16 4.960e+10

NFORM conserva il valore all’interno del terminale, chiudendolo diventa indefinita. È possibile annullarla (sdefinirla?, undefinirla?) con export NFORM=

Ma si può semplificare, con uno script (particolare, in qualche misura atipico), ecco nprec

#!/bin/bash
if [ -z $1 ]; then
    export NFORM=''
else
    export NFORM='%.'$1'e'
fi
echo $NFORM

e ottengo

$ source nprec 2
%.2e
$ echo $NFORM
%.2e
$ source nprec

$ echo $NFORM

$

La particolarità è che lo script va eseguito con source.

Da formato scientifico all’usuale – 2ns

#!/bin/bash
R=""
while [[ $# -gt 0 ]]; do
    N=$(echo $1 | sed -e 's/\./\,/')
    N=$(printf "%.30f" $N)
    ye=$(echo $N | grep -c "\,")
    if [[ $ye == 1 ]]; then
        N=$(echo $N | sed -e 's/[0]*$//')
        N=$(echo $N | sed -e 's/\,$//')
    fi
    N=$(echo $N | sed -e 's/\,/\./')
    R=$R' '$N
    shift
done
echo $R  | xclip -f -selection clipboard

La trasformazione della rappresentazione del numero è svolta da printf.

Notare l’if che controlla se il numero ha una parte decimale; se sì sed elimina gli 0 finali e se il piunto è finale anche questo viene eliminato. Queste operazioni richiedono di trattare un numero alla volta, sempre il primo ($1) perché l’ultima istruzione del loop while è shift che, appunto, elimina l’elemento appena trattato.

$ 2ns 9.4607e15
9460700000000000
$ 2ns 3.0857e16
30857000000000000
$ 2ns 1.49597870700e+11
149597870700

OK, funziona, con numeri non troppo grandi (sorry Mr Scrooge!)

$ 2ns 1.23e+25
12300000000000000000000000
$ 2ns 1.23e+29
123000000000000000002281701376
$ 2ns 1.23e+30
1229999999999999999988457275392
$ 2ns 1.23e+35
122999999999999999998102457678823424
$ 2ns 1.23e+40
12300000000000000000542639153683841941504

OOPS! la precisione, a un certo punto non sono più 0 tondi ma altre cifre 😡 Ma tanto questi numeri sono illeggibili (o meglio io non riesco a leggerli). Da fare (prossimamente?) per renderli più amichevoli: separare con spazi le cifre in gruppi di 3 come usano i commerciali, volendo anche con i punti proprio come fanno i Rag.

Linguaggi, quali?

dtkx

Marco Rogers rockz! 💥; sa di essere “pretty much the best guy you know“, ha tanti follower, conosce über-nerds che seguo anch’io e potrei continuare con tante altre positività che poi risulterebbe un post di pubblicità.

Invece no, qualcuno ha risposto a un suo tweet quasi sondaggio e, manco fossi Dirk Gently, sono arrivato a lui e al tweet: If I was learning a first programming language today, here are the ones I would consider.

La sua lista è in buona parte condivisibile rispetto al mercato. Tanti oggi sono sul Web e allora JavaScript è la risposta, con contorno di CSS. Altrimenti Python, è il suo momento, specie considerando NumPy, Matplotlib &co; e anche TensorFlow (sempre &co. ma ne so poco, anzi niente). Ruby non so se è un’impressione solo mia ma ultimamente non è più così gettonato. Java e C++ se invece si fanno cose più impegnative; non entrambi, scegliere, secondo me. Si potrebbe aggiungere Rust, in futuro diventerà obbligatorio almeno considerarlo. Il C liscio solo se si si hanno le mani nel kernel, o si è come dire? di gusti molto particolari.

 Manca qualcuno? Ecco io direi che 

Marco ha già, in pochissimo tempo, ottenuto tante risposte, provo a scorrerle…

  • Manca il C#: Marco non lo pratica, io nemmeno.
    JS da assuefazione: It was my first language and I found it difficult moving to other languages yrs later.
  • Aggiunge Looking back on my early CS edu, it feels important that my whole first semester was only pseudo-code e We wrote on paper mostly e The pseudo-code languages had rules. But they were all abstract. We weren’t learning by blind trial and error, but by discussion and feedback on every solid attempt.
    Ancora: I also attribute a lot of these early experiences with helping me be comfortable learning new languages. (Not that it was “easy”).
  • JavaScript is the most accessible and most easily distributed language in history. The web is the greatest invention in the last several decades and you have to use JavaScript to program a large portion of web clients. Purtroppo è così! (opinione personale). That may change if web assembly takes off.
  • If you wanna always be able to get a job. You should learn JavaScript and web development.
    Both Python and Ruby are super easy to get into. Pretty accessible to learn. Have big communities and lots of resources. Popular and we’ll supported frameworks and toolchains. Pick either one. You’ll be fine.
    ava is the other language for web development. It has all of the benefits of community, support, marketability, etc. It is just an entirely different learning experience as a language. And you’ll tend to gravitate towards completely different kinds of problems.

E altro ancora. E altro arriverà.

Ma è ora di svelare chi mi ha passato la dritta: In risposta a @polotek.

Peter Seibel, quello di Practical Common Lisp, uno dei libri della mia top ten, anzi 5.

Di Peter un cinguettio da quotare interamente: My radical position is that the first item should include both Scheme and Common Lisp. If you think they’re the same, you’re doing it wrong. And if you think one is better than the other, you’re doing it extra wrong.

Marco continua con condizioni di mercato, molto sensate imho. E niente Lisp, ovviamente.

Altro ancora e… Language wars in 5… 4… 3… 2… 1…

OK, non so se sono solo io che se entro in queste discussioni non riesco più a uscirne. Ma adesso smetto, ecco. Ah! ‘ncora ‘na roba: sono con Peter (100%) anche se appena nomino il Lisp perdo punti; Racket poi è anche peggio 👽

Il valore della funzione in Bash

PETSCIIBOTS-1

Gli script Bash sono comodi, versatili, utilissimi ma a volte… Esempio (puramente didascalico, non realistico, nèh!): il prodotto di due numeri, in Python posso scriverlo così (file per.py):

#:/usr/bin/python3

def per(na, nb):
    res = na * nb
    return res

# test

r1 = per(6, 7)
print(r1)

r2 = per(3.7, 7.3)
print(r2)

Un pythonista produrrebbe un codice più sintetico ma sono sul didattico. Eseguendolo ottengo

$ python3 per.py
42
27.01
$

OK, funziona sia con gli interi che con i reali. In Bash c’è l’istruzione return ma non è quello che serve: ritorna l’exit code della funzione, un intero compreso in [0..256]. In effetti in Bash l’equivalente di return degli usuali linguaggi non manca, bisogna ricorrere a una qualche rimedio, ce n’è almeno uno facilmente usabile: usare una variabile, viene vista globalmente, il codice diventa (file per.sh):

#/bin/bash

function per () {
    res=$(echo "$1 * $2" | bc -l)
}

# test

per 6 7
echo $res

per 3.7 7.3
echo $res

Eseguo:

$ bash per.sh
42
27.01
$

Credo serva qualche chiarimento.

  • la definizione do funzione inizia con function seguita da nome, coppia di parentesi () senza gli argomenti e graffa aperta {. Nel nostro caso function per () {. È però possibile omettere la parola function, la riga diventa: per () {;
  • gli argomenti all’interno della funzione vengono visti come $1, $2, ...;
  • nel caso in esame siamo costretti a usare bc perché vogliamo trattare (anche) numeri reali; inoltre il carattere * verrebbe interpretato da echo per cui occorre racchiudere l’espressione tra virgolette. Ma posso usare varianti, p.es: res=$(echo $1 "*" $2 | bc -l), res=$(echo $1 \* $2 | bc -l);
  • al posto di $(…) posso usare la vecchia sintassi, sconsigliata da POSIX: res=`echo $1 \* $2 | bc -l`;
  • la funzione viene chiamata con il nome seguito dai parametri separati da spazio, p.es: per 6 7;
  • la variabile usata per ritornare il valore della funzione, in questo caso res, è visibile al ritorno dalla funzione.

È possibile avere funzioni che ritornano più valori, ecco magmin.sh:

#/bin/bash

magmin () {
    st=$1
    res1=${st^^}
    res2=${st,,}
}

# test

magmin "Stringa MAG e min 💥"
echo $res1
echo $res2

che produce

$ bash magmin.sh
STRINGA MAG E MIN 💥
stringa mag e min 💥
$

Nota: si può evitare l’uso della variabile di supporto st, l’espressione res1=${1^^} funziona ma, secondo me, è meno chiara.

Quindi, in conclusione: è possibile usare funzioni in Bash come negli altri linguaggi di script (Python, JavaScript, …). Ma è pratico? Con tutte le alternative liberamente disponibili è da valutare caso per caso.

Messaggio colorato nel terminale – 2

Diablefaucheur

Continuo con i messaggi colorati, completando questo post.

Oltre a Bash e Python manca qualcuno? In realtà molti ma in particolare JavaScript, per me Node.

Si può fare, anzi è una variante di quanto già visto. Va però considerato che JavaScript è nato e viene usato per il Web e solo raramente in locale per il terminale. Ma volendo… 😋

Il beep ha richiesto una sessione prolungata di  googlaggio  duckduckgoaggio, non funziona il solito \a delle origini di Unix. Anche perché a rappresenta “alarm” ma c’è dentro ^G. C’è il modulo beeper, ed ecco:

#!/usr/bin/node
var beeper = require('beeper');
beeper()

Il modulo dev’essere installato via npm. Dove abbiamo trovato l’indicazione di beeper? Purtroppo non lo abbiamo memorizzato. Ma guardando dentro si trova una soluzione più semplice, senza moduli aggiunti, molto simile alle versioni Bash e Pyton:

#!/usr/bin/node
console.log('\u0007');

eh sì! ^G.

Per il colore lo script (dis.js) è una variante di quello Python, dopo aver concatenato gli elementi della lista degli argomenti usa replace() per le sostituzioni. Notare la sintassi inusuale, derivata dalle regexps. Ed ecco:

#!/usr/bin/node

RED='\033[01;31m';
GREEN='\033[01;32m';
YELLOW='\033[01;33m';
BLUE='\033[01;34m';
ENDC='\033[00m';

args = process.argv;
n = args.length - 1;
i = 2
st = args[i];
while (i < n) {
    i += 1;
    st = st + ' ' + args[i];
}

st = st.replace(/%R/g, RED);
st = st.replace(/%G/g, GREEN);
st = st.replace(/%Y/g, YELLOW);
st = st.replace(/%B/g, BLUE);
st = st.replace(/%Z/g, ENDC);

console.log(st);

js

Messaggio colorato nel terminale – 1

kk2

Se un messaggio è importante deve attrarre l’attenzione. Argomento già trattato in passato ma questa è la versione 2018.

Ci sono due modi facili: un suono e un colore diverso dal solito.

Produrre un beep è semplice, così:

#!/bin/bash
echo -en "\a"

o così:

#!/usr/bin/python3
print("\a", end="")

Visualizzare il testo colorato è risultato immediato con Python, più impegnativo con Bash. I codici delle sequenze di escape ANSI si trovano (come farei senza la Wiki) qui: ANSI escape code – Colors.

La versione Python:

#!/usr/bin/python3

import sys

RED='\033[01;31m'
GREEN='\033[01;32m'
YELLOW='\033[01;33m'
BLUE='\033[01;34m'
ENDC='\033[00m'
BEEP='\a'

dis = str.join(' ', sys.argv[1:])

dis = dis.replace('%R', RED)
dis = dis.replace('%G', GREEN)
dis = dis.replace('%Y', YELLOW)
dis = dis.replace('%B', BLUE)
dis = dis.replace('%Z', ENDC)
dis = dis.replace('%_', BEEP)

print(dis)

 

I codici sono nella forma %L dove %L è una lettera (o anche più di una, o _ per il beep) che genera una sequenza improbabile: di solito % finisce la parola, terminando un numero.

Più impegnativo produrre l’equivalente Bash: abbiamo, PLG e io, provato con sed ma con pessimi risultati. Poi la dritta, da Stack Overflow ed ecco:

#!/bin/bash

st=$*
st=$(printf '%s' "$st" | sed -e 's/:R/\\033[01;31m/g')
st=$(printf '%s' "$st" | sed -e 's/:G/\\033[01;32m/g')
st=$(printf '%s' "$st" | sed -e 's/:Y/\\033[01;33m/g')
st=$(printf '%s' "$st" | sed -e 's/:B/\\033[01;34m/g')
st=$(printf '%s' "$st" | sed -e 's/:Z/\\033[00m/g')

echo -e $st

sh
Rispetto alla versione Python cambia il codice, uso : al posto di %. Probabilmente da uniformare. Prossimamente, forse, chissà 😐

log – qual è la base?

Recentemente mi sono imbattuto in quella che è diventata una delle mie ossessioni preferite. Adesso vi conto, così la supero, forse. C’era da indovinare come inserire i dati in una formula di cui avevamo una descrizione incompleta e una tabella di (pochi) valori. Si può fare? proviamo in due, indipendentemente, e otteniamo una buona correlazione ma con parametri differenti. Dal confronto salta fuori che è colpa del logaritmo. Cioè di che base usare.

Io in questi casi mi rivolgo alla Wiki. Ottima, esauriente, come di solito.

Ho chiesto agli amici di Facebook:

Se sono Veri Matematici è il logaritmo naturale, quindi in base e. Se c’è gente relativamente normale (relativamente, perché chi parla di logaritmi dal salumiere?) è il logaritmo in base dieci (e in questo caso il logaritmo naturale si scrive ln come sulle calcolatrici. Nota che gli informatici non parlano di logaritmo in base 2, perché dicono semplicemente bit.

e

Già, si dice che chi usa log per indicare logaritmo naturale poi usi Log per indicare il logaritmo in base 10, ma io non l’ho mai visto fare (perché chi usa log al posto di ln in realtà usa solo log, le altre basi sono per gli ingegneri).

Ah! sì, ricordo, quando usavo le calcolatrici, come nell’immagine lassù. E prima ancora il Bruhns per topografia (alle superiori ho frequentato l’istituto per geometri, il Guarini a Torino).

Ma me l’ero scordato; tutti (quasi) i linguaggi di programmazione usano base e. Ma allora perché a scuola (tutte?) si usa base 10? Non credo ci sia ancora chi li usi per moltiplicazioni, divisioni, radici ed elevazione a potenza. O anche qui sono male informato?

A proposito dei linguaggi c’è chi è davvero smart, ecco Python:

math.log(x[, base])

With one argument, return the natural logarithm of x (to base e).
With two arguments, return the logarithm of x to the given base, calculated as log(x)/log(base).

$ py3
>>> import math
>>> math.log(10)
2.302585092994046
>>> math.log(100)
4.605170185988092
>>> math.log(10, 10)
1.0
>>> math.log(100, 10)
2.0
>>> math.log(math.exp(1))
1.0
>>> math.log(9, 3)
2.0
>>> math.log(27, 3)
3.0
>>> math.log(42, 4)
2.6961587113893803
>>> 4**_
42.000000000000014

Bravo Python ma non è il solo, ecco Racket:

(log z [b]) → number

  z : number
  b : number = (exp 1)

Returns the natural logarithm of z. The result is normally inexact, but it is exact 0 when z is an exact 1. When z is exact 0, exn:fail:contract:divide-by-zero exception is raised.

If b is provided, it serves as an alternative base. It is equivalent to (/ (log z) (log b)), but can potentially run faster. If b is exact 1, exn:fail:contract:divide-by-zero exception is raised.

$ rkt
Welcome to Racket v6.11.
> (log 10)
2.302585092994046
> (log 100)
4.605170185988092
> (log 10 10)
1.0
> (log 100 10)
2.0
> (log (exp 1))
1.0
> (log 9 3)
2.0
> (log 27 3)
3.0
> (log 42 4)
2.6961587113893803
> (define le (log 42 4.2))
> le
2.6044944060210176
> (expt 4.2 le)
42.0

Per tutti gli altri la regola è semplice log_b(z) = log(z) / log(b).

Ossessione superata 😁 E tutti i link che ho trovato li butto, fatto 😁

Convertire codice Fortran in Python – 2

Continuo da qui.

Questione preliminare: perché non usare le liste?
Risposta: le liste sono per elementi eterogenei e sono lente e avide di memoria. Se i dati sono omogenei (p.es. tutti int o tutti float o casi simili) ci sono gli array mono- e pluri-dimensionali (matrici).

Python ha un modulo per la gestione degli arrays, ma c’è di meglio: NumPy.

Ogni trattazione di questo argomento inizia con un avvertimento: attenzione alla gestione degli indici, e continua con l’esempio Fortran “per colonna” invece del più comune “per riga”. OK, da verificare. C’è poi un altro aspetto cui prestare attenzione, anzi due. In Fortran il primo indice è 1; in Pthon 0; lo sanno tutti, è banale ma è facile fare confusione, esempio:

$ py3
>>> import numpy as np
>>> ar = np.arange(10)
>>> ar
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> ar.shape
(10,)
>>> ar.size
10
>>> ar[0]
0
>>> ar[5]
5
>>> ar[ar.size]
Traceback (most recent call last):
  File "", line 1, in 
IndexError: index 10 is out of bounds for axis 0 with size 10
>>> ar[ar.size - 1]
9

OK? Quando si ha una matrice attenzione ala gestione degli indici, dicono tutti. Da verificare in Fortran (ebbene sì! ma è semplice e c’è una piacevole sorpresa).

Gestione indici per per riga, file row_col.f

      program row_col

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(row, col) = m(row, col) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o row-col row-col.f
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,016s
user	0m0,012s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,018s
user	0m0,014s
sys	0m0,004s
$ time ./row-col
   11.0000000       220.000000       10001.0000
   5.50000000       110.000000       5000.50000

real	0m0,019s
user	0m0,012s
sys	0m0,008s

Uh! per un milione di dati float, real*4 in Fortran 77, il tempo di esecuzione è troppo piccolo per essere significativo. E questa è la versione lenta, ecco quella corretta, col-row.f

      program col_row

      integer row, col
      real m(1000,1000)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = col + 10 * row
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      do row = 1, 1000
          do col = 1, 1000
              m(col, row) = m(col, row) / 2
          end do
      end do

      print*, m(1, 1), m(2, 200), m(1000, 1)

      end

Compilo ed eseguo:

$ gfortran -o col-row col-row.f
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,018s
user	0m0,018s
sys	0m0,000s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,005s
sys	0m0,013s
$ time ./col-row
   11.0000000       2002.00000       1010.00000
   5.50000000       1001.00000       505.000000

real	0m0,017s
user	0m0,013s
sys	0m0,004s

sì, migliore! di quanto? pochissimo!

Non siamo più ai tempi del PDP-11, nemmeno a quelli del VAX suo successore e la RAM oggi è chip e abbondante. Ecco perché il Web è pieno di foto di gatti, mica come ao tempi di Lenna!

OK, questo per il Fortran, entri Python, con Numpy.

Prima versione col-row.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[col, row] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[col, row] = M[col, row] / 2

print(M[0, 0], M[1, 199], M[999, 0])

Eseguo:

$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,811s
user	0m0,798s
sys	0m0,012s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,807s
user	0m0,781s
sys	0m0,020s
$ time py3 col-row.py
11.0 2002.0 1010.0
5.5 1001.0 505.0

real	0m0,798s
user	0m0,776s
sys	0m0,016s

Seconda versione row-col.py

#!/usr/bin/python3

import numpy as np

M = np.arange(1000*1000, dtype=float).reshape(1000,1000)
for row in range(1000):
    for col in range(1000):
        M[row, col] = col + 1 + 10 * (row + 1)

print(M[0, 0], M[1, 199], M[999, 0])

for row in range(1000):
    for col in range(1000):
        M[row, col] = M[row, col] / 2

print(M[0, 0], M[1, 199], M[999, 0])
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,771s
user	0m0,755s
sys	0m0,016s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,823s
user	0m0,800s
sys	0m0,024s
$ time py3 row-col.py
11.0 220.0 10001.0
5.5 110.0 5000.5

real	0m0,794s
user	0m0,774s
sys	0m0,020s

sì, Python è lento ma ampiamente accettabile.

OK, ho tutto quello che serve per la conversione; con Python sarà semplice aggiornare e aggiungere nuove funzionalità, i grafici, ma non solo. Per esempio capita (ahemm, sempre) di dover fare la matrice trasposta, quella con righe e colonne scambiate. A me che sono vecchio vedere come fa NumPy vengono le vertigini:

$ py3
>>> import numpy as np
>>> M = np.arange(12).reshape(3,4)
>>> M
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> M.T
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

Il manuale di NumPy è qui.

OK? vero che Python è (quasi) l’unico linguaggio in città. O almeno quello più sexy 🤩

Convertire codice Fortran in Python – 1

Si può fare ma c’è qualche aspetto da considerare.

Intanto le abitudini di una volta, complice una possibilità del Fortran non presente in Python. Nel Fortran (parlo delle versioni vecchie 77 e precedenti; il 77 è tuttora usato per quel che ne so) ci sono due tipi di sottoprogrammi: le funzioni e le subroutines. Queste ultime, affini alle funzioni C di tipo void erano di gran lunga le preferite. C’è una giustificazione: le variabili sono passate per indirizzo (reference, non so se i due termini sono rigorosamente equivalenti, questi erano quelli correnti). Esempio:

N = 8
CALL DOPPIO (N)
PRINT* N

SUBROUTINE DOPPIO (N)
N = 2 * N
END

Il risultato che ottengo è 16, cioè il valore di N modificato nella subroutine viene conservato nel main, è cambiato il valore memorizzato all’indirizzo della variabile e questo è quello che ho passato al sottoprogramma. Con Python (per quel che ne so) questo non è possibile, ecco qua:

$ py3
>>> def doppio (n):
...   n = 2 * n
...   print('in doppio:', n)
...
>>> n = 8
>>> doppio (n)
in doppio: 16
>>> print (n)
8

alla funzione doppio ho passato il valore 8 e al di fuori di questa il valore di n resta 8 indipendentemente da quanto avvenuto nel sottoprogramma. Ovvia la soluzione, la stesa da sempre, in Fortran:

N = 8
N = IDOPPIO (N)
PRINT* N

FUNTION IDOPPIO (N)
N = 2 * N
RETURN (N)
END

Notare che ho cambiato il nome alla funzione, variabili e funzioni sono di default REAL*4 tranne quelle il cui nome inizia con le lettere dell’intervallo [I..N] che sono INTEGER*2 o INTEGER*4 a seconda della versione e delle opzioni di compilazione. Caratteristico del Fortran è che la precisione viene espressa in byte e non in bit.

In Python  la funzione può ritornare una lista, torna comoda per le subroutines:

$ py3
>>> def pot(a, b, p):
...   a = a ** p
...   b = b ** p
...   return a, b
...
>>> r = pot(3, 4, 2)
>>> print(r)
(9, 16)
>>> # ovviamente
... a, b = pot(3, 4, 2)
>>> print(a)
9
>>> print(b)
16

Quindi una prima regola pratica: scrivere funzioni che ritornano i valori delle variabili che ci servono. Ma ci sono dei casi in cui questa pratica non basta, il caso di arrays e matrici di dimensioni rilevanti.

In Fortran c’è l’istruzione COMMON:

COMMON /DATI/ A, B(10), C, D
COMMON /MAT/ P(20, 20), R(10, 10, 10)

Ogni funzione o subroutine che usi A, B, C o D dovrà dichiarare il COMMON /DATI/, idem per il caso di /MAT/.

In Python si ottiene un risultato simile (meno granulare, meno raffinato ma forse meno soggetto a bug) con le variabili globali:

$ py3
>>> #a = 0
>>> b = []
>>>
>>> def popola_dati():
...   global a
...   a = 42
...   for c in range(10):
...     b.append(c ** 2)
...
>>>
>>> popola_dati()
>>> print('a =', a)
a = 42
>>> print('b =', b)
b = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

OK, notare che a devo dichiararla global nella funzione a differenza di b che dev’essere inizializzato nel main e diventa automaticamente globale. b è una lista, non esattamente l’equivalente degli arrays e matrici del Fortran. Le liste sono versatili ma onerose, da modificare, in un post successivo, pausa 😎