Category Archives: Python

GEB.G ovvero come ho scoperto la ricorsività nella programmazione

gebIo sono in ferie. Nel senso che sto tentando di tenermi lontano dal ‘puter per qualche giorno, disintossicarmi. E cosa si fa in questi casi? Altro, per esempio rileggere i classici. Io sono alle prese con Gödel, Escher, Bach di Douglas Hofstadter (1979, versione italiana 1984).

Ogni volta che lo rileggo salta fuori qualcosa di nuovo. O, come nei giorni scorsi un ricordo (attenzione: nostalgia qui) di cose successe all’inizio degli anni ’80, peccato non aver tenuto appunti e sorgente dei programmi! (ma sono passati più di trent’anni).
Allora il mio ‘puter, il Pr1me 550, un mini voleva che si usasse principalmente il Fortran. Vero che c’erano linguaggi di scipting come il CPL, Cobol e RPG per i gestionali, Basic per Giorgio ma erano complementari. E i capi insistevano per usare il IV (1964) meno esoso di risorse del 77 (1978).
DRH invece parla di ricorsività, vietata nel Fortran, prendi questo pezzo copiato da p.146-148 (sperando che l’autore e il sgnor Adelphi non si arrabbino):

Possiamo definire strutture geometriche infinite usando lo stesso metodo, cioè quello di espandere un nodo dopo l’altro. Per esempio, definiamo un diagramma infinito chiamato “Diagramma G”. Useremo a questo scopo una rappresentazione implicita. In due nodi scriveremo semplicemente la lettera ‘G’, che starà per una copia del Diagramma G. Nella Figura 31a abbiamo disegnato il Diagramma G implicitamente.

g1

Ora, se vogliamo vedere il Diagramma G più esplicitamente, possiamo espandere ognuna delle due G, cioè, sostituirle con lo stesso diagramma, ma in dimensioni ridotte (vedi Fig. 31b). Questa versione “di secondo ordine” del diagramma G ci dà una vaga idea dell’aspetto che avrebbe il Diagramma G se fosse completato, cosa che è impossibile.

g2

Nella Figura 32 si vede una parte ancora più grande del Diagramma G, in cui i nodi sono stati numerati, cominciando dal basso e da sinistra a destra. In basso sono stati inseriti due nodi supplementari che portano i numeri 1 e 2.
[...]
Ma il Diagramma G possiede proprietà ancora più sorprendenti. Tutta la sua struttura può essere racchiusa in un’unica definizione ricorsiva che è la seguente:

g3Come fa questa funzione G(n) a racchiudere la struttura ad albero di G? È molto semplice: se si costruisce un albero nel quale si colloca G(n) al di sotto di n per tutti i valori di n, si ritrova il Diagramma G. È proprio così che ho scoperto il Diagramma G la prima volta. Stavo indagando la funzione G e, mentre cercavo un metodo rapido per calcolarne i valori, mi venne l’idea di disporre i valori già noti in un albero. Con mia grande sorpresa, trovai che questo albero ammetteva quella descrizione geometrica ricorsiva estremamente ordinata.

Etc.
Uh! mi ero detto, chissà se… A quei tempi era normale avere un contratto di assistenza, tipo che l’hard disk andava in crash un paio di volte all’anno (fare spesso il back-up!) e conoscevi bene i tecnici. Io mi ero fatto dare un nastro con una versione non proprio ufficiale del Lisp e me ne sono innamorato, e perdura ancora. Tanto che l’altra sera arrivato a p.148 del GEB mi sono detto: ma la G cos’è già che fa, e se…
Ecco (g.nl):

#!/usr/bin/newlisp

(define (g n)
	(if (= 0 n) 
		0
		(- n (g (g (- n 1))))))

;; main
(set 'n (int (main-args 2)))
(print n " -> " (g n) "\n")
(exit)

gnl

Sì, lo so che il Lisp non è che piaccia proprio a tutti e allora ecco Python, il Basic di adesso, ormai la ricorsività non è più un taboo (g.py):

#!/usr/bin/env python3

from sys import argv

def g(n):
	if n == 0:
		return 0
	else:
		return n - g(g(n - 1))

#main
n = int(argv[1])
print(n, '->', g(n))

gpy
OK?
Ma però –ricordo perfettamente– mi disse teh Boss: la ricorsività costa!
Vero. Riscriviamo lo script Python à la Fortran –potrei installarlo ma sapete che sono in ferie :wink:– (g-nonric.py):

#!/usr/bin/env python3

from sys import argv

n = int(argv[1])
g = [0]
for c in range(1, n + 1):
	g.append(c - g[g[c - 1]])
print(n, '->', g[n])

Adesso g non è più una funzione ma una lista, in Fortran sarebbe un array. A proposito allora gli array si chiamavano vettori se monodimensionali e matrici se con due o più dimensioni, oggi i giovani mi hanno cambiato anche il vocabolario :shock:

Non cambia niente ma i pythonisti per bene userebbero la list comprehension, lo script precedente sarebbe così:

#!/usr/bin/env python3

from sys import argv

n = int(argv[1])
g = [0]
[g.append(c - g[g[c - 1]]) 
	for c in range(1, n + 1)]
print(n, '->', g[n])

Quindi torniamo all’osservazione del capo di allora (se vi dicessi che il suo nome inizia per G ci credereste?), ecco:

cpy

Quasi 17 secondi contro meno di 3 centesimi! G, teh Boss ha ragione, ancora adesso. Lo so che non interessa ma newLISP ha la possibilità di creare l’eseguibile, vediamo:

cnl

Nessun vantaggio. Però basta nostalgia e basta ‘puter, sono in ferie :wink:

Il percorso (path) del nome del file – Windows

n5

A completamento del post precedente sul path del nome del file ecco un po’ di prove eseguite su Windows 8.1. Non ancora definitive ma ho avuto poco tempo (e sono pasticcione ma questo non lo ammetterò mai!). Mi è stato detto che la versione otto non piace, gli utenti seri si fanno installare la sette (sì si può, pare, anche se non ne so niente e il mio agente che doveva raccontare tutto su queste cose è troppo impegnato su cose più redditizie).

Le prove svolte, usando lo script del post precedente senza modifiche, sono in parte positive ma restano zone da esplorare. Avevo pensato di installare anche py2exe che trasforma lo script Python in un eseguibile ma non ho avuto il tempo.

In ambiente Windows non esistono i link e non si può attivare lo script à la chmod, pertanto le prove consistono nel lanciare lo script attraverso Python o tramite un batch (nome improprio ma è la loro terminologia).

Lancio dello script nella directory in cui si trova:

0

Quasi tutto OK; notare che ho usato / (slash) come separatore di percorsi, è possibile, ma Python li converte in \ (backslash). Inoltre la splitext() usa la convenzione C in cui backslash è un carattere speciale. Su questo dovrò ancora indagare.

Lancio dello script che si trova in un’altra posizione:

1

Caso simile al precedente.

Creazione di un batch-file e lancio tramite questo:

Creo il file pn.bat contenente quest’unica riga di testo:

c:/python/python e:/w-p-name/p-name.py $*

2

Notare l’errore di cui mi sono accorto troppo tardi: al posto di $ dovevo usare %.
Poi ho pasticciato, ho dimenticato un pezzo del path ottenendo un errore; dopo la correzione tutto OK.

Lancio del batch da un’altra posizione:

3

OK. Notare che gli identificativi dei dischi vengono gestiti regolarmente come se fossero parte del path, esattamente come i punti di mount su Linux.
Inoltre c’è la questione dell’equivalenza minuscolo/maiuscolo nei nomi; anche i dischi vengono convertiti in minuscolo.

In conclusione, pare quasi tutto OK, sono in ritardo sul programma :mrgreen:

Il percorso (path) del nome del file

n4

Un postino noioso e didascalico. Ma credo mi tornerà utile per l’applicazione iniziata nel post precedente. Che continuerà, ferie permettendo.
Ah! sì, oggi solo Linux, cioè dovrebbe essere tutto OK anche con Windows ma ancora non ho potuto verificarlo, prossimamente riferirò.

Ecco p-name.py lo script Python che verrà usato in diversi modi per confrontare le risposte e poter scegliere quelle che più ci soddisfano:

#!/usr/bin/env python3

import sys, os

nl = '\n'
pname = sys.argv[0]
epname = os.path.abspath(pname)
realname = os.path.realpath(epname)
pdir = os.path.dirname(epname)
pbase = os.path.basename(epname)
pext = os.path.splitext(epname)
print(nl, pname, nl, epname, nl, realname, nl,
      pdir, nl, pbase, nl, pext)

if len(sys.argv) > 1:
    print('***', sys.argv)

Semplice vero? In ogni caso scrive su terminale le seguenti stringhe:

  • il nome dello script;
  • il nome assoluto dello script, partendo cioè dalla radice;
  • il nome reale dello script, vedremo quando differisce dal precedente;
  • il nome della directory dove si trova lo script;
  • il nome dello script senza il percorso;
  • l’estensione del nome dello script (come elemento #1 di una tupla), cosa usuale per Windows.

Se si passano parametri sulla riga di comando (in alcuni casi ho usato la stringa t) viene poi scritta la lista dell’intera stringa di invocazione; la condizione usata non sempre funziona come previsto, da indagare.

Il modo più immediato di lanciare lo script è attraverso Python, uso la versione 3, rispetto alla 2 cambiano solo le print che vogliono le parentesi.

0

Tutto come previsto, caso banale.

Usualmente una volta che lo script funziona è normale abilitarlo con chmod (cosa non possibile su Windows)

1

vediamo che l’output è indistinguibile dal precedente.

In questo caso è normale togliere l’estensione .py dal nome del comando, ottengo

2

questo che continua a essere tutto come previsto.

Altra cosa usuale per Linux è la creazione di un link simbolico e usare il nome di questo al posto di quello dello script:

3

In questo caso si vede la differenza tra abspath() –punta al nome del link– e realpath() –punta al nome dello script.

Spesso, quasi sempre in realtà, lo script da eseguire non è nella directory corrente, niente panico:

4

Una cosa sull’uso di ~ per indicare la home: in questo caso è la shell a convertirla e lo script la vede “giusta”. Se invece usassimo ~ all’interno di Python dovremmo dereferenziarla con os.path.expanduser(), come già visto parecchie volte in passato.

Adesso il caso più generico: un link (simbolico, li faccio sempre così) in una directory contenuta nella path che punta a uno script da qualche altra parte lanciato da un’altra posizione ancora.

5

Tutto come previsto, ma notare l’espansione del nome del link.
Per cui posso concludere che è tutto tranquillo ma occorre fare attenzione alla differenza tra abspath() e realpath(). Nel mio caso (futuro) avrò bisogno di accedere a usa sub-directory che si trova nella stessa directory dello script. Dovrò pertanto usare realpath(). E questo vale anche per il nome dello script.

OK, il post di oggi è davvero palloso. Ma devo pubblicarlo, così poi me lo ricordo. Volendo voi potete non leggerlo, nèh! :wink:

Un piccolo progetto PyGTK: ng – 1

rosettaE se… È da tanto che non faccio qualcosa di grafica e pensare che tanto tempo fa avevo fatto tutta una tiritera su GTK3+ e poi c’è the Freeman al quale l’ho quasi promesso.

Ecco l’amico the Freeman –ehi! ha visto la luce (nel senso di Linux) grazie (anche) a me!

Però venti e più anni di Poli + Windows lasciano il segno. Forse è inevitabile, e poi mica tutti devono smanettare allo stesso modo. E salta fuori che nel suo ultimo post (ma ne sta già preparando un altro mi dice WordPress): Universo Linux vs Pianeta Winapple: testdrive “virtuale”

passa in rassegna millemila distro e guardate come giudica Lubit:


ID Distro     ISO     R<1     HD     UI    US
Lubit         0,67    1       LIVE   1     5

Tutto passa per il tasto destro del mouse. Interfaccia minimale [...]

Il post dovreste leggerlo tutto, tutti, ma per chi è troppo indaffarato ecco il significato di due dei numerini:

UI – Usabilità Interfaccia: 1: interfaccia decisamente spartana con navigazione “a tendine”.
US – Usabilità Sistema Operativo: 5: risposta immediata, velocità nella apertura delle applicazioni, assenza lag in caso di più app aperte.

Chiaro? Alb the Freeman continua a sentire la mancanza di poter riempire il desktop di skifetse icone. Ne abbiamo già parlato, in diversi social-cosi, anche se sono solo ambasciatore mi sono fatto sgridare e richiamare all’ordine da Bit3Lux, Squittymouse et al. più volte.

Tutto ciò premesso provo a impostare una soluzione, di compromesso, chissà…

dock

Che ne direste di una toolbar come quella in figura, dove sia possibile inserire i programmi che usate abitualmente? un dock insomma. Minimale, fatto da me, ampiamente modificabile.

Per adesso c’è solo un abbozzo, ancora non fa niente di utile, tranne che chiudersi facendo click sull’ultimo tasto quello con la X.

#!/usr/bin/python3

from gi.repository import Gtk

button_names = [
    Gtk.STOCK_ABOUT,
    Gtk.STOCK_ADD,
    Gtk.STOCK_REMOVE,
    Gtk.STOCK_QUIT]

buttons = [Gtk.ToolButton.new_from_stock(name) for
			name in button_names
		  ]

def on_click_me_clicked(button):
	print(button.id)
	if button.id == 4:
		print('ciao')
		Gtk.main_quit()

toolbar = Gtk.Toolbar()
n = 0
for button in buttons:
    n += 1
    button.id = n
    toolbar.insert(button, -1)
    button.connect("clicked", on_click_me_clicked)

style_context = toolbar.get_style_context()
style_context.add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR)

window =  Gtk.Window(decorated = False)
window.add(toolbar)
window.move(2000, 24)
window.connect('delete-event', Gtk.main_quit)
window.show_all()

Gtk.main()

Le immagini, per adesso usa quelle nello stock ma non dovrebbe essere un problema, ecco una prova

#!/usr/bin/python3

from gi.repository import Gtk

img = Gtk.Image.new_from_file('./ath.png')

window =  Gtk.Window()
window.add(img)
window.connect('delete-event', Gtk.main_quit)
window.show_all()

Gtk.main()

imgL’evento da associare ai pulsanti non dovrebbe essere un problema: per aprire un programma basta (dovrebbe bastare) digitarne il nome proprio come si fa da terminale e per aprire un documento con il programma predefinito c’è il comando xdg-open (controllate con man per saperne di più). Questo per Linux, per Windows il comando corrispondente è –oops! prossimamente, devo accedere a una macchina con quella roba sopra :wink:
Mi dicono (Stack Overflow) dovrebbe essere start, da verificare.

Prossimamente assemblo il tutto, non subito, sono in ferie :grin:

Uh! immagini da Rosetta, il mio robot preferito (a pari merito con Cassini, Opportunity, i Voyager e altri ancora) e Max Planck Gesellschaft.

Backup solo se… adesso con Python – 2

Continuo da qui.

n3

click

E finisco per adesso. Nel senso che la versione da terminale è a posto, funziona (pare). Quando tornerà il mio giovane aiutante –anzi committente– G. attualmente in vacanza con i nonni e senza PC vedrò se lo soddisfa. Restano anche da fare: 1) la verifica su Windows (in settimana, probabilmente); e 2) montare un’interfaccia grafica (prossimamente, forse).

tag

Copia in una directory scelta i file elencati nel file di configurazione tag-orig che si trova nella stessa directory del programma. Una copia di questo file di configurazione lo trovate nel post sopraindicato.

Può essere lanciato in due modi, come dallo screenshot qui sotto.

#!/usr/bin/python3

import sys, os.path, shutil

def getorig():
    progname = sys.argv[0]
    progdir = os.path.dirname(progname)
    fbk = os.path.join(progdir, "tag-orig")
    inpf = open(fbk, "r")
    txt = inpf.readlines()
    inpf.close()
    ele = []
    for st in txt:
        sst = st.strip()
        if len(sst) > 0:
            sst = os.path.expanduser(sst)
            fn = os.path.abspath(
                os.path.join(progdir, sst))
            ele.append(fn)
    return ele

def getbkdir():
    if len(sys.argv) > 1:
        dest = os.path.abspath(sys.argv[1])
        if not os.path.isdir(dest):
            dest = ''
    else:
        dest = os.getcwd()

    return dest    

ORIG = getorig()
BKDIR = getbkdir()

if len(BKDIR) == 0:
    print('***', BKDIR, 'non esiste')
    sys.exit(1)

for fn in ORIG:
    print(fn)
    if os.path.isfile(fn):
        sz = os.path.getsize(fn)
        dest = os.path.join(BKDIR, os.path.basename(fn))
        if sz > 7:
            print("\tcopio", fn, "in", dest)
            shutil.copyfile(fn, dest)

tag
agpa
è quello raccontato qui.
OK, forse serve un piccolissimo manuale:

tag [dest]

se dest è una directory (di solito su un drive USB) copia i file elencati nel file di configurazione tag-orig presente nella stessa directory dello script in dest, se non presente la directory di destinazine è quella corrente (quella di $PWD).

r-tag

Copia i file precedentemente salvati con tag nelle loro posizioni originai. Non sovrascrive i file vecchi ma li rinomina aggiungendo a ognuno il suffisso “-old“.

#!/usr/bin/python3

import sys, os.path, shutil

def getorig():
    progname = sys.argv[0]
    progdir = os.path.dirname(progname)
    fbk = os.path.join(progdir, "tag-orig")
    inpf = open(fbk, "r")
    txt = inpf.readlines()
    inpf.close()
    ele = []
    for st in txt:
        sst = st.strip()
        if len(sst) > 0:
            sst = os.path.expanduser(sst)
            fn = os.path.abspath(
                os.path.join(progdir, sst))
            ele.append(fn)
    return ele

def getbkdir():
    if len(sys.argv) > 1:
        bkdir = os.path.abspath(sys.argv[1])
        if not os.path.isdir(bkdir):
            bkdir = ''
    else:
        bkdir = os.getcwd()

    return bkdir    

ORIG = getorig()
BKDIR = getbkdir()

if len(BKDIR) == 0:
    print('***', BKDIR, 'non esiste')
    sys.exit(1)

for fn in ORIG:
    print(fn)
    bkfile = os.path.join(BKDIR, os.path.basename(fn))
    if os.path.isfile(fn):
         shutil.copyfile(fn, fn + '-old')
    if os.path.isfile(bkfile):
        print("\tcopio", bkfile, "in", fn)
        shutil.copyfile(bkfile, fn)

Non metto l’immagine dello screenshot, è molto simile alla precedente e ugualmente incomprensibile.

Manuale minimo:

r-tag [bkdir]

copia i file di back-up presenti nella directory corrente o quella specificata con il parametro bkdir nelle loro posizioni originali, come definite dal file tag-orig. I file vecchi vengono rinominati aggiungendo il suffisso "-old".

a-tag

Accoda ai file indicati nel file di configurazione tag-orig le aggiunte con lo steso nome ma con il prefisso “a-“.

#!/usr/bin/python3

import sys, os.path, shutil

def getorig():
    progname = sys.argv[0]
    progdir = os.path.dirname(progname)
    fbk = os.path.join(progdir, "tag-orig")
    inpf = open(fbk, "r")
    txt = inpf.readlines()
    inpf.close()
    ele = []
    for st in txt:
        sst = st.strip()
        if len(sst) > 0:
            sst = os.path.expanduser(sst)
            fn = os.path.abspath(
                os.path.join(progdir, sst))
            ele.append(fn)
    return ele

def getbkdir():
    if len(sys.argv) > 1:
        bkdir = os.path.abspath(sys.argv[1])
        if not os.path.isdir(bkdir):
            bkdir = ''
    else:
        bkdir = os.getcwd()

    return bkdir    

ORIG = getorig()
BKDIR = getbkdir()

if len(BKDIR) == 0:
    print('***', BKDIR, 'non esiste')
    sys.exit(1)

for fn in ORIG:
    print(fn)
    aggfile = os.path.join(BKDIR, 'a-' + os.path.basename(fn))
    if os.path.isfile(aggfile):
        print("\taggiungo", aggfile, "a", fn)
        f = open(aggfile, 'r')
        agg = f.read()
        f.close()
        f = open(fn, 'a')
        f.write(agg)
        f.close()

Manuale:

a-tag [aggdir]

accoda ai file inidcati nel file di configurazione tag-orig i file con lo stesso nome ma con prefisso "a-" presenti nella directory aggdir; se aggdir non è specificata viene assunta quella corrente.

Non so voi ma nello scrivere questo post mi si sono sviluppati i brufoli :mrgreen:

Martedì 22 approfittando della disponibilità dell’amico Alberto ho fatto qualche prova con Windows, tutto OK :grin:

Backup solo se… adesso con Python – 1

n2
Continuo da qui. Ma cambio tutto. Con bash, shell di Linux, usata negli script dei post precedenti abbiamo visto che non è facilissimo. O meglio, non è come siamo abituati. E poi c’è un grosso limite: non funziona con Windows.
E se provassimo a usare Python?
Sì è un’idea, traduciamo l’ultima versione dello script. Una cosa: da parecchio ormai di Python ne esistono due versioni: la 2.x che continua a essere usatissima per lo più per inerzia e la 3.x, ormai giunta a 3.4; userò questa. Peraltro le differenze (per quello che dobbiamo fare) sono minime.

Ecco la versione iniziale dello script:

#!/usr/bin/python3

import os.path, shutil

ORIG = ["./orig-1/f-1",
        "./orig-1/f-2-piccolo",
        "./orig-2/f1-3",
        "./orig-3/f1 4",
        "./orig-3/f2-4 manca"
       ]

DEST = "./dest/"

for fn in ORIG:
    print(fn)
    if os.path.isfile(fn):
        sz = os.path.getsize(fn)
        dest = DEST + os.path.basename(fn)
        if sz > 7:
            print("\tcopio", fn, "in", dest)
            shutil.copyfile(fn, dest)

t1

OK. L’unica modifica da apportare per utilizzare la versione 2 di Python è di togliere le parentesi a print() che in 2.x è un’istruzione e in 3.x una funzione.

ORIG è adesso una lista (righe 5-10), i componenti sono stringhe. DEST è invece una stringa.

In pratica questo script non è facilmente usabile: i file da copiare sono codificati all’interno dello stesso script, idem la directory di destinazione e resta da vedere cosa capita con nomi più generici.
L’elenco dei file da back-up-pare propongo di memorizzarlo in un file ad hoc, chiamato tag-orig, così:

./orig-1/f-1
./orig-1/f-2-piccolo

~/lab/OKp/lug/Tag-3/orig-2/f1-3
/home/juhan/lab/OKp/lug/Tag-3/orig-2/f1 4

./orig-3/f2-4 manca

Notare che i nomi dei file possono essere relativi alla directory dello script (i primi 2), con l’uso della ~ per indicare la home (il terzo) e assoluti (il quarto). Inoltre ci possono essere righe vuote. Anzi se uno è pasticcione può scrivere commenti: la riga relativa non sarà un file esistente e verrà ignorata.

Ecco lo script per gestirlo:

#!/usr/bin/python3

import os.path, sys

progname = sys.argv[0]
print(sys.argv, progname)
progdir = os.path.dirname(progname)
print(progdir)
fbk = os.path.join(progdir, "tag-orig")
print("file bk =", fbk)
inpf = open(fbk, "r")
ORIG = inpf.readlines()
inpf.close()
print(len(ORIG), ORIG)
print("\n")

for st in ORIG:
    sst = st.strip()
    if len(sst) > 0:
        sst = os.path.expanduser(sst)
        fn = os.path.abspath(
            os.path.join(progdir, sst))
        print(fn)

t12

La path del programma si trova in sys.argv[0], il percorso del file tag-orig viene ricavato usando join() del modulo os.path. di questo modulo utilizziamo anche abspath() ed expanduser(). Quest’ultima si rende necessaria per risolvere ~. Non sono ancora riuscito a provarlo con Windows (G il mio valido aiutante è in vacanza con i nonni e si vede che ha di meglio da fare, beato lui!) ma dovrebbe funzionare (riferirò in futuro).

La directory di destinazione DEST invece dev’essere gestita in modo diverso, secondo me. Propongo due alternative:

  • la directory corrente, quella che con Linux otteniamo con il comando pwd e con Windows con cd;
  • la directory che passiamo allo script come primo argomento; da racchiudere tra apici se contine spazi.

Ritengo che il primo modo sia quello più pratico, pensando a drive (penne) USB, ma chissà…

#!/usr/bin/python3

import os, sys

if len(sys.argv) > 1:
    DEST = os.path.abspath(sys.argv[1])
else:
    DEST = os.getcwd()

print(DEST)

qui

Non resta che inserire questi due pezzi nello script iniziale. Argomento della prossima puntata che se faccio tutto adesso poi di cosa vi racconto? :wink:

Bailey-Borwein-Plouffe

wormsmalltNon ho ancora finito, anzi sarà una storia lunga, ma ho trovato serendipicamente un esercizio, un test per vedere se ho capito qualcosa finora.
La scintilla è stato questo tweet: A shocking formula to calculate any digit of Pi
Sembra fattibile, così a prima vista.

bbpPer sicurezza provo con un linguaggio sensato, Python:

#!/usr/bin/python
# -*- coding: utf-8 -*-

piprec = 3.0 # valore sensato, secondo me ;-)

pi = 0.0
for k in range(10):
    pi += 1. / 16 ** k * (4. / (8 * k + 1) - 2. / (8 * k + 4) -
                          1. / (8 * k + 5) - 1. / (8 * k + 6))
    dpi = pi - piprec
    print k, pi, dpi
    piprec = pi

bbp0

OK. Funziona. E adesso mx.

Ecco, non è che sono ancora praticissimo, anzi, ma provo. Comincio a impratichirmi, l’ho già detto che sono niubbo?

bbp1

OK, dimenticato la maiuscola per Print, ma sembra che Sum si possa usare, si può distribuire anche su più linee.

E (forse) si può anche scrivere la funzione in un file, verifico (ts.mx):

r = Sum[
        a + a^2,
        {a, 1, 3}
    ];
Print["il risultato è ", r]

bbp2Forse si può migliorare (ts1.mx):

r = Sum[
        a + a^2,
        {a, 1, 3}
    ];
Print["il risultato è ", r];

bbp3

no, devo ancora impratichirmi ma il calcolo c’è.

Non è nemmeno una soluzione migliore l’uso della pipe:

bbp4

OK, sono pronto, provo, ecco (bbp.mx):

Sum[
    1. / 16 ^ k (
        4. / (8 k + 1) - 2. / (8 k + 4) -
        1. / (8 k + 5) - 1. / (8 k + 6)),
    {k, 0, 9}
]

bbp5

Cool! provo a alzare il limite superiore della sommatoria, 100 invece di 9:

bbp6

Ho provato a variare il limite, per la mia configurazione questo è 12

 k     pi
 9 - 3.14159265358979115
10 - 3.14159265358979313
11 - 3.14159265358979323
12 - 3.14159265358979324

Poi non varia più. Da qualche parte c’è scritto come aumentare la precisione ma sono ancora troppo niubbo.

Ancora una cosa, risultato di una telefonata non su questo argomento bensì sui linguaggi di programmazione. Ce ne sono tanti, e ne vengono proposti di nuovi continuamente. Si usa quello che fa al caso nostro, anche in funzione dei propri gusti e preferenze; per esempio io sono affezionato a calc:

bbp7Si possono fare degli script con una sintassi C-like, ecco la versione calc di bbp.

#!/usr/bin/calc -f

/* bbp.cal - Bailey-Borwein-Plouffe formula */

piprec = 3.0;
pi = 0.0;
for (k = 0; k < 11; k++) {
	pi = pi + 1. / 16 ** k * (4. / (8 * k + 1) - 2. / (8 * k + 4) -
			       			  1. / (8 * k + 5) - 1. / (8 * k + 6));
	dpi = pi - piprec;
	print k, pi, dpi;
	piprec = pi;
}

bbp8

Domanda: Perché calc?
Risposta: Perché no? :shock:

Ah, sì! Sembra che mx (Mathics) funzioni :cool:

Un quiz di Annarita

Header

Vi voglio raccontare di una mia disavventura programmatica multipla. Però non dovete ridere troppo di me, anche perché comunque c’è l’happy end finale.

Sto studiando mx, ahemmm Mathics e quando ho visto il post della prof. Annarita Ruberto: Trova Il Numero Misterioso mi son detto: “ecco l’occasione per testare la mia conoscenza di mx!“. Disastro, devo ancora impratichirmi, parecchio.

Ma, quando il gioco etc… perché non provare con Python? OK, disastro anche qui.
Perché non avevo capito il senso del testo. E cominciavo a disperarmi. Anzi panico proprio. Non sono più quello di una volta! OK, basta frasi fatte e commiserazioni. Ieri Annarita ha pubblicato (con un giorno di ritardo su quanto aveva minacciato) la soluzione, spiegando i passaggi in modo che capissi anch’io (sigh!): Trova Il Numero Misterioso: La Soluzione E Altre Curiosità.

Insomma questo è il problema:

1. la somma delle cifre di x è un numero y, che, moltiplicato per il numero ottenuto invertendo le sue cifre, dà come prodotto x.
2. Trovando i fattori primi del numero ottenuto invertendo le cifre di x, poi calcolando la semisomma dei quadrati di questi fattori primi, e, infine, rimuovendo la cifra 0 dal nuovo numero, si otterrà il numero x.

E questa la soluzione:

Il numero 1729 soddisfa la condizione 1 del puzzle perché:
la somma delle sue cifre è 19 e 19 ∙ 91 = 1729.
1729 soddisfa anche la condizione 2 perché:
9271 = 73 ∙ 127
e
(73^2 + 127^2) / 2 = 10729. Rimuovendo lo zero da questo numero, si ottiene proprio 1729.

Ah! ma allora, presto, anzi prima di subito, con Python e in futuro (forse) con mx Mathics!

numbers
Solo un paio di osservazioni prima del listato:

  • C’è bisogno di trovare i fattori di un numero, ho trovato una soluzione che mi piace sul solito Stack Overflow: Prime factorization – list. Non l’ho analizzata, funziona, forse in futuro… L’autore ha un blog interessante, su argomenti affini, avendo tempo…;
  • Visto che continuavo a ripetere sempre le stesse istruzioni ho deciso di trasformarle in funzioni, anche quando sono cortissime. Tutto in previsione della futura versione mx (davvero conto di farla, non subito, devo impratichirmi);
  • Queste funzioni sono lispiche, potrebbero essere chiamate in sequenza come usano i funzionali; non l’ho fatto per tenere il codice leggibile (e inserire una marea di print in fase di debug);
  • Inoltre mi sono convertito alla setta degli utilizzatori della list comprehension: è leggibile, comoda, si risparmia una riga. Peggio per quei linguaggi che non ce l’hanno;
  • Per adesso è un abbozzo ma queste funzioni le ho raggruppate in un file che forse evolverà in modulo come si deve.

OK, ecco il codice, addirittura commentato, cosa rara per me.

Il modulo con le funzioni utilizzate:

# -*- coding: utf-8 -*-

# jutils: qui dentro ci metto le funzioni che mi trovo
#         a ricopiare sovente; chissà se funziona

def strlist(st):
    ''' ritorna la lista dei caratteri della stringa st'''
    return list(st)

def ls2str(ls):
    ''' ritorna la stinga unione della lista ls'''
    st = ''
    return st.join(ls)

def lsstr2dig(ls):
    ''' ritorna le cifre della stringa ls'''
    return [int(c) for c in ls]

def strpopc(st, c):
    ''' se la stringa st contiene il carattere
        c questo viene eliminato; una volta sola
    '''
    p = st.find(c)
    if p >= 0:
        st = st[:p] + st[p+1:]
    return st

def factors(n):
    ''' copiato da qui:
        http://stackoverflow.com/questions/ +++
             16996217/prime-factorization-list
    '''
    gaps = [1, 2, 2, 4, 2, 4, 2, 4, 6, 2, 6]
    length, cycle = 11, 3
    f, fs, next = 2, [], 0
    while f * f <= n:
        while n % f == 0:
            fs.append(f)
            n /= f
        f += gaps[next]
        next += 1
        if next == length:
            next = cycle
    if n > 1: fs.append(n)
    return fs

Lo script:

#!/usr/bin/python
# -*- coding: utf-8 -*-

from jutils import *

def passo_1(numero):
    ls = strlist(str(numero)) # lista di caratteri
    n = lsstr2dig(ls)         # lista di cifre
    somma = sum(n)            # somma delle cifre
    ls = strlist(str(somma)) # idem per la somma
    ls.reverse()
    rst = ls2str(ls)
    r = int(rst)              # e quindi in numero
    return (somma * r) == numero

def passo_2(numero):
    ls = strlist(str(numero)) # lista di caratteri
    ls.reverse()              # inverto
    rinv = ls2str(ls)         # trasformo la lista in stringa
    ninv = int(rinv)          # questo è il numero inverso
    fattori = factors(ninv)
    lquad = [i**2 for i in fattori] # lista dei quadrati dei fattori
    nquad = sum(lquad) / 2
    strquad = str(nquad)
    strquad = strpopc(strquad, '0')
    cont = int(strquad)
    return cont == numero

#main
trovato = False
numero = 999
numerofinale = 9999
while not trovato:
    numero += 1
    ok1 = passo_1(numero)
    if numero == numerofinale:
        print "Mi arrendo"
        exit()
    if ok1:
        trovato = passo_2(numero)
        if trovato:
            print "Trovato!", numero

arquiz

Ancora PI, poi basta per un po’

AthenaPeiraeusStampAllora … ultima puntata (per ora, ci sarebbe altro materiale ma non vorrei sembrare monomaniaco) sulle cifre di π, o PI, o pi, o –lui insomma.

Questa volta ricavato e memorizzato su file formattato usando sympy.
Non ci sono arrivato da solo anche perché c’è Stack Overflow. A volte mi chiedo come facevo una volta prima del Web! OK, <mode nostalgia OFF>. Trovato qui: 1000 digits of pi in python, in particolare la risposta di Garret Hyde. Che cita Python Adventures, lo stesso post di cui parlavo la volta scorsa (il mondo è piccolo, anzi no, è enorme ma se ti riesci a orientare :wink:)
Ecco il codice:

#!/usr/bin/python
# -*- coding: utf-8 -*-

def riga_numeri(bl, n):
    f.write(bl + ' ')
    for c in range(n / 5):
        f.write('{:>5} '.format((c + 1) * 5))
    f.write('\n')

def num_col(n):
    f.write('{:>8} '.format(n))

from sympy.mpmath import mp

mp.dps = 1000001  # number of digits
PI = mp.pi
# print(PI)	  # print pi

lunriga = 100 #per TR

f = open("pag-sy", "w")
bl = " " * 8
riga_numeri(bl, lunriga)
n = 0
st = str(PI)

for c in st[2:]:
    if (n % lunriga) == 0:
        num_col(n)
    f.write(c)
    n += 1
    if (n % lunriga) == 0:
        f.write("\n")
    elif (n % 5) == 0:
        f.write(" ")
f.write("\n")
f.close()

pi1Msy

Undici minuti sul mio ‘puter non recentissimo, anzi… Al solito utilizza pienamente una delle CPU presenti:

cpu

E, ovviamente, produce lo stesso risultato di pi, componente della libreria CLN, utilizzato qui: Contare le cifre di PI.

diff

Ops! diff mi dice che c’è un newline di differenza, verificabile anche con ls:

ls

A qualcuno interessa un file di 1.3 MB, abbondante, questo:

dump

Dai con la prossima settimana si cambia argomento :roll:

Tanti decimali, il caso di PI

1986_stamp_83d40m_Athena_Recentemente Ok, panico ha partecipato al Carnevale della Matematica tenuto dall’ottimo GLF :-)
Uno scoop: il carnevale porta pochissime visite, appena visibili se si vanno a cercare ma devi metterci molta cura. Forse perché l’argomento era difficile, troppo teorico? Ma io ho partecipato anche con l’altro blog, quello più generalista, il Tamburo Riparato. Di là era per pubblicizzare Linux, risultati molto scarsi, un flop.
Ma l’argomento m’interessa, anzi lo script originale lo rivelo solo oggi.
E non finisce qui: ho altra roba già in bozza, mica posso buttarla :roll:

Ecco la versione naïf per calcolare PI con tante cifre decimali quanto volete. Usa sympy e non è ottimale, anzi: è lentissimissima:

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, sympy

ndec = int(sys.argv[1]) + 2
# evalf restituisce il numero di cifre,
#compreso il 3 e il punto  iniziale

pi_str = str(sympy.pi.evalf(ndec))
print pi_str

pisym

OK, le ultime cifre non sono attendibili, come si vedrà in seguito. Inoltre è lentissimo; vediamo cosa capita per un caso più sostanzioso, 1 milione di decimali:

pitime

No, non può andare 10 minuti e 32 secondi! Ma quando il gioco si fa :oops: già detto, cancello.

Un sito da prendere in considerazione in questi casi è Rosetta. Sì, c’è il nostro caso, qui.

#!/usr/bin/python
# -*- coding: utf-8 -*-

def calcPi():
    q, r, t, k, n, l = 1, 0, 1, 1, 3, 3
    while True:
        if 4*q+r-t < n*t:
            yield n
            nr = 10*(r-n*t)
            n  = ((10*(3*q+r))//t)-10*n
            q  *= 10
            r  = nr
        else:
            nr = (2*q+r)*l
            nn = (q*(7*k)+2+(r*l))//(t*l)
            q  *= k
            t  *= l
            l  += 2
            k += 1
            n  = nn
            r  = nr

import sys

ndec = int(sys.argv[1]) + 1
pi_digits = calcPi()

i = 0
for d in pi_digits:
    sys.stdout.write(str(d))
    i += 1
    if i == ndec: print; sys.exit(0)

rosetta1

scrive il 3 iniziale, problema minore. Per quanto riguarda la velocità c’è sempre di mezzo il print, per velocizzare si potrebbe mettere in una stringa (problema aggiuntivoo: in Python le stringhe sono immutabili). In ogni caso ecco:

rotime

Niente, l’ho interrotto, dopo 200 minuti. Cosa succede se tolgo l’output commentando la riga 30? Continua a essere molto lento. Mistero, per adesso meglio la mia versione naïf :shock:
Però, che razza di codice! Chissà da dove viene? Ecco, entrano in gioco la Wiki e Google e prima di subito la soluzione:

OK, ma: 1) non è in Python e 2) Haskell? di nuovo?

Beh, Haskell forse in futuro, per Python c’è Laszlo Szathmary: Digits of PI (Part 2).

Non so se l’argomento interessa solo me; in ogni caso conto di parlarne ancora.
Ah! intanto ho scoperto che c’è un clone FOSS di Mathematica, scritto in Python; interessa? a me sì ovviamente.
Prossimamente… forse… :roll:

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 71 follower