Panico!
Ebbene sì! Ci vuole coraggio per postare dopo l’intervento di ieri di glipari: ha reso Haskell sensato e –diciamola tutta– comprensibile 8)
Ma faccio finta di non accorgermi di niente e continuo con le mie robe 8)
Poi c’è un altro motivo per cui è stato difficile fare queste cose semplici-semplici: difficili da spiegare ai ggiovani –beati loro. Ma se leggete fino in fondo ci sarà la soluzione.
OK, dopo queste doverose premesse, vado e continuo con le mie osservazioni al post di dikiyvolk Compilati VS interpretati passo 1.
Nel post Verifica sul sort di ‘Compilati vs. Interpretati’ – 1 avevo provato a verificare le affermazioni di dikiyvolk (Walter ma in russo non ci sono parole più normali per un nickname sexy?) relative a Python 2.x e 3.x, confermandole in toto.
Ma non possiamo dimenticare newLISP. Perché mi è simpatico, è un mio amico (chissà con Haskell incombente come andrà a finire?) e allora ecco una versione modificata dello script di dikiyvolk, con relativi risultati. Riporto un solo caso rappresentativo, sempre sul solito PC athena.
#!/usr/bin/newlisp
(println "Lettura: " (time (set 'data (parse (read-file "LCbig.txt") "\n" 0))) " ms")
(println "Sort: " (time (sort data)) " ms")
(println "Scrittura: "(time (write-file "LCbig-out.txt" (join data "\n"))) " ms")
(exit)
OK, niente sorprese, newLISP fa il suo lavoro, onestamente. Il piccolino non delude, mai 8)
Ma, e qui vengo al dunque, per tanto tempo, quando ero giovane io, il linguaggio di programmazione era un altro, solo uno, quello.
Sì il Fortran. Si può fare in Fortran? Quasi-quasi ci provo!
Io, per quanto riguarda il Fortran non sono molto aggiornato. Nell’ambito professionale non sono mai andato oltre alla versione del ’78, il Fortran 77. Poi dal ’90 in poi ci sono state nuove versioni ma, almeno fino al 2000 si è continuato con quella. Anche perché i fortranisti macinano numeri e le librerie erano intoccabili (un esempio tra poco).
Allora, primo ostacolo: il Fortran 77 non c’è predefinita una subroutine per il sort; te la devi scrivere o, meglio ancora, trovarne una efficiente scritta da qualcuno più bravo di te. È quello che ho fatto io: googlata per fortran sort e –presto!– ecco:
Uh! vediamo 8)
lcpsort.f95: sembra bella, recente (2009), viene dall’università, ha riferimenti a tesi di dottorato, intrigante ma ha un difetto (per me): mi toccherebbe aggiornarmi sulle specifiche del Fortran moderno. No dai, vediamo se c’è qualcos’altro. Però è davvero scritta bene, mi piacerebbe lavorare con gente che scrive così; mi sento un po’ in colpa di non dare nemmeno un occhiata al PDF indicato;
quick_sort1.f: qui siamo sempre in ambito universitario, qualche anno prima (1994). Si tratta della trascrizione in Fortran di una procedura Algol (antenato del Pascal). Siccome la versione originale era in Fortran IV (o 66 come indicato nei commenti, i due termini sono quasi sinonimi) c’è un uso notevole di GOTO. Ha inoltre un’altra pecca: sorta interi e aggiustarla per trattare stringhe non è immediato (ci ho provato). Da notare, verso la fine, il commento fuorviante relativo alla ricorsione: non è vero, in Fortran non c’era e non c’è nella subroutine. Scritta come si usava una volta è difficile farsi un’idea di come funzioni, davvero.
quick_sort2.f: di Leonard J. Moss del SLAC. Fa la stessa cosa del precedente ma con uno stile decisamente migliore (imho). Scritta nell”86 partendo dal Knuth (ottima scelta!). Notare l’uso ragionevole dei GOTO (in Fortran 77 è difficile non usarne mai) e la scrittura ordinata. È immediato modificarla per ordinare un array di stringhe, come risulta dal codice riportato sotto.
Qui sono stato preso da una serie di dubbi; provo a elencarli.
In Fortran (ricordate che sto sempre parlando delle versioni che conosco) non c’è un istruzione per leggere un file in blocco, si deve leggere una riga o un record per volta. In realtà non risulterà un handicap come temevo.
Le stringhe in Fortran sono di lunghezza fissa, la definisci tu ma una volta dichiarata è quella: se leggi un numero minore di caratteri viene riempita con spazi alla fine; se leggi troppi caratteri perdi quelli in esubero alla capacità della stringa.
Problema relativo alla dimensione dei dati da trattare: duecento mega abbondanti! Questo per me è stato uno shock difficile da accettare e raccontare: il mio primo computer aveva mezzo mega di RAM (sì 512 KB di RAM) e un hard disk di 80 MB, ne ho parlato qui, c’è anche la foto. Sigh 8)
In ogni caso i parametri da passare alla subroutine devono essere modificati. Metto in due common i vettori (ai miei tempi gli array si chiamavano così, da noi) dei dati e dei puntatori. Sì, lo so, non sono puntatori, qui non ci sono ma si comportano esattamente come se lo fossero (sto parlando da vecchio fortrainer, sapevatelo).
Ecco con pochissime correzioni (le istruzioni originali sono state lasciare commentandole) il codice
c234567
Program wsort
parameter (numlinee = 5 700 000)
common /datistr/ lines
common /ordine/ index
character*102 lines(numlinee)
dimension index(numlinee)
open(1, file = 'LCbig.txt')
* open(1, file = 'T0.txt')
read(1, 90, err = 10, end = 20) (lines(i), i = 1, 5 700 000)
close(1)
10 print*, i
stop
20 numlines = i-1
write(*, 91) numlines
call SORTRX(numlines)
open(2, file = 'LCbig-o1.txt')
do 30 i = 1, numlines
write(2, 90) lines(index(i))
30 continue
close(2)
90 format(A)
91 format('Lette ', I10, ' linee')
92 format(A)
end
C From Leonard J. Moss of SLAC:
C Here's a hybrid QuickSort I wrote a number of years ago. It's
C based on suggestions in Knuth, Volume 3, and performs much better
C than a pure QuickSort on short or partially ordered input arrays.
SUBROUTINE SORTRX(N)
* ,DATA,INDEX)
C===================================================================
C
C SORTRX -- SORT, Real input, indeX output
C
C
C Input: N INTEGER
C DATA REAL da me trasformato in string
C
C Output: INDEX INTEGER (DIMENSION N)
C
C This routine performs an in-memory sort of the first N elements of
C array DATA, returning into array INDEX the indices of elements of
C DATA arranged in ascending order. Thus,
C
C DATA(INDEX(1)) will be the smallest number in array DATA;
C DATA(INDEX(N)) will be the largest number in DATA.
C
C The original data is not physically rearranged. The original order
C of equal input values is not necessarily preserved.
C
C===================================================================
C
C SORTRX uses a hybrid QuickSort algorithm, based on several
C suggestions in Knuth, Volume 3, Section 5.2.2. In particular, the
C "pivot key" [my term] for dividing each subsequence is chosen to be
C the median of the first, last, and middle values of the subsequence;
C and the QuickSort is cut off when a subsequence has 9 or fewer
C elements, and a straight insertion sort of the entire array is done
C at the end. The result is comparable to a pure insertion sort for
C very short arrays, and very fast for very large arrays (of order 12
C micro-sec/element on the 3081K for arrays of 10K elements). It is
C also not subject to the poor performance of the pure QuickSort on
C partially ordered data.
C
C Created: 15 Jul 1986 Len Moss
C
C===================================================================
parameter (numlinee = 5 700 000)
common /datistr/ data
common /ordine/ index
character*102 data(numlinee)
dimension index(numlinee)
* INTEGER N,INDEX(N)
* REAL DATA(N)
INTEGER LSTK(31),RSTK(31),ISTK
INTEGER L,R,I,J,P,INDEXP,INDEXT
* REAL DATAP
character*102 DATAP
C QuickSort Cutoff
C
C Quit QuickSort-ing when a subsequence contains M or fewer
C elements and finish off at end with straight insertion sort.
C According to Knuth, V.3, the optimum value of M is around 9.
INTEGER M
PARAMETER (M=9)
C===================================================================
C
C Make initial guess for INDEX
DO 50 I=1,N
INDEX(I)=I
50 CONTINUE
C If array is short, skip QuickSort and go directly to
C the straight insertion sort.
IF (N.LE.M) GOTO 900
C===================================================================
C
C QuickSort
C
C The "Qn:"s correspond roughly to steps in Algorithm Q,
C Knuth, V.3, PP.116-117, modified to select the median
C of the first, last, and middle elements as the "pivot
C key" (in Knuth's notation, "K"). Also modified to leave
C data in place and produce an INDEX array. To simplify
C comments, let DATA[I]=DATA(INDEX(I)).
C Q1: Initialize
ISTK=0
L=1
R=N
200 CONTINUE
C Q2: Sort the subsequence DATA[L]..DATA[R].
C
C At this point, DATA[l] <= DATA[m] <= DATA[r] for all l < L,
C r > R, and L <= m <= R. (First time through, there is no
C DATA for l < L or r > R.)
I=L
J=R
C Q2.5: Select pivot key
C
C Let the pivot, P, be the midpoint of this subsequence,
C P=(L+R)/2; then rearrange INDEX(L), INDEX(P), and INDEX(R)
C so the corresponding DATA values are in increasing order.
C The pivot key, DATAP, is then DATA[P].
P=(L+R)/2
INDEXP=INDEX(P)
DATAP=DATA(INDEXP)
IF (DATA(INDEX(L)) .GT. DATAP) THEN
INDEX(P)=INDEX(L)
INDEX(L)=INDEXP
INDEXP=INDEX(P)
DATAP=DATA(INDEXP)
ENDIF
IF (DATAP .GT. DATA(INDEX(R))) THEN
IF (DATA(INDEX(L)) .GT. DATA(INDEX(R))) THEN
INDEX(P)=INDEX(L)
INDEX(L)=INDEX(R)
ELSE
INDEX(P)=INDEX(R)
ENDIF
INDEX(R)=INDEXP
INDEXP=INDEX(P)
DATAP=DATA(INDEXP)
ENDIF
C Now we swap values between the right and left sides and/or
C move DATAP until all smaller values are on the left and all
C larger values are on the right. Neither the left or right
C side will be internally ordered yet; however, DATAP will be
C in its final position.
300 CONTINUE
C Q3: Search for datum on left >= DATAP
C
C At this point, DATA[L] <= DATAP. We can therefore start scanning
C up from L, looking for a value >= DATAP (this scan is guaranteed
C to terminate since we initially placed DATAP near the middle of
C the subsequence).
I=I+1
IF (DATA(INDEX(I)).LT.DATAP) GOTO 300
400 CONTINUE
C Q4: Search for datum on right <= DATAP
C
C At this point, DATA[R] >= DATAP. We can therefore start scanning
C down from R, looking for a value <= DATAP (this scan is guaranteed
C to terminate since we initially placed DATAP near the middle of
C the subsequence).
J=J-1
IF (DATA(INDEX(J)).GT.DATAP) GOTO 400
C Q5: Have the two scans collided?
IF (I.LT.J) THEN
C Q6: No, interchange DATA[I] <--> DATA[J] and continue
INDEXT=INDEX(I)
INDEX(I)=INDEX(J)
INDEX(J)=INDEXT
GOTO 300
ELSE
C Q7: Yes, select next subsequence to sort
C
C At this point, I >= J and DATA[l] <= DATA[I] == DATAP <= DATA[r],
C for all L <= l < I and J < r <= R. If both subsequences are
C more than M elements long, push the longer one on the stack and
C go back to QuickSort the shorter; if only one is more than M
C elements long, go back and QuickSort it; otherwise, pop a
C subsequence off the stack and QuickSort it.
IF (R-J .GE. I-L .AND. I-L .GT. M) THEN
ISTK=ISTK+1
LSTK(ISTK)=J+1
RSTK(ISTK)=R
R=I-1
ELSE IF (I-L .GT. R-J .AND. R-J .GT. M) THEN
ISTK=ISTK+1
LSTK(ISTK)=L
RSTK(ISTK)=I-1
L=J+1
ELSE IF (R-J .GT. M) THEN
L=J+1
ELSE IF (I-L .GT. M) THEN
R=I-1
ELSE
C Q8: Pop the stack, or terminate QuickSort if empty
IF (ISTK.LT.1) GOTO 900
L=LSTK(ISTK)
R=RSTK(ISTK)
ISTK=ISTK-1
ENDIF
GOTO 200
ENDIF
900 CONTINUE
C===================================================================
C
C Q9: Straight Insertion sort
DO 950 I=2,N
IF (DATA(INDEX(I-1)) .GT. DATA(INDEX(I))) THEN
INDEXP=INDEX(I)
DATAP=DATA(INDEXP)
P=I-1
920 CONTINUE
INDEX(P+1) = INDEX(P)
P=P-1
IF (P.GT.0) THEN
IF (DATA(INDEX(P)).GT.DATAP) GOTO 920
ENDIF
INDEX(P+1) = INDEXP
ENDIF
950 CONTINUE
C===================================================================
C
C All done
END
La lunghezza massima delle stringhe (102) è stata determinata con questo script Python
#!/usr/bin/ env python
# -*- coding: utf-8 -*-
import time
try:
# lettura del file
t0 = time.time()
print "Tempo inizio: ", time.strftime("%H:%M:%S")
file_input = open("LCbig-out.txt", "ru")
lines = file_input.readlines()
file_input.close()
t1 = time.time()
print "Tempo fine: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, "secondi"
# controllo
lung = 0
nlines = len(lines)
print "controllo", nlines
t0 = time.time()
print "Tempo inizio: ", time.strftime("%H:%M:%S")
for c in range(0, nlines):
if len(lines[c]) > lung:
lung = len(lines[c])
print c, lung
t1 = time.time()
print "Tempo fine: ", time.strftime("%H:%M:%S")
print "Tempo di controllo dei dati: ", t1 - t0, "secondi"
print "linea più lunga", lung
except(IOError):
print "Errore nell'elaborazione del file LCbig-out.txt"
quit()
Notare inoltre come sia possibile scrivere bene i numeri grossi: siccome il compilatore elimina tutti gli spazi (tranne quelli all’interno di stringhe letterali) il numero 5700000 può essere scritto come 5 700 000. Il compilatore converte in maiuscolo tutto il codice (con l’eccezione ricordata prima) per cui io continuo a usare i caratteri minuscoli.
La versatilità del common mi consente di chiamare le linee da ordinare lines nel main e data nella subroutine.
La riga 12 meriterebbe un lungo discorso, gestisce l’errore, l’endfile, contiene quello che in Fortran si chiama DO implicito (DO è quello che tutti chiamano for).
Ecco cosa succede
Il file di output ha queste dimensioni
Troppo grosso, sì perche le stringhe sono paddate con blank fino alla lunghezza di 102 caratteri. Ecco uno script per sblankarle
#!/usr/bin/ env python
# -*- coding: utf-8 -*-
import time
try:
# lettura del file
file_input = open("LCbig-o1.txt", "ru")
lines = file_input.readlines()
file_input.close()
t1 = time.time()
nlines = len(lines)
# sblank
print "Tempo inizio: ", time.strftime("%H:%M:%S")
t0 = time.time()
for c in range(0, nlines):
lines[c] = lines[c].strip() + '\n'
t1 = time.time()
print "Tempo fine: ", time.strftime("%H:%M:%S")
print "Tempo di sblank dei dati: ", t1 - t0, "secondi"
# scrivo
file_output = open("LCbig-o3.txt", "w")
file_output.writelines(lines)
file_output.close()
except(IOError):
print "Errore nell'elaborazione del file dei dati"
quit()
Oh! anche il file originale aveva degli extra blank a destra! Modifico lo script e eseguo, ecco il risultato
OK. E questo è quanto. Non avrei mai immaginato di fare una cosa così in Fortran, non credevo fosse possibile, le dimensioni se ci penso mi fanno provare, ancora oggi, una vertigine difficile da descrivere.
In ogni caso certe cose non si devono fare in Fortran. Molto più semplice con Python. E stupendamente semplice e fenomenale newLISP.
Vero? 8) Non so se ci crederete ma scrivere questo post è stato per me difficile, quasi come una seduta psicoanalitica.