Quando si scrive del codice un per un programma, di solito si scrivono delle righe di testo, dove ogni riga rappresenta un comando. Più il programma diventa complesso, più le righe di codice aumentano: si aggiungono comandi, si correggono errori, si torna indietro sul codice scritto e lo si modifica.
Ogni giorno che salviamo il nostro codice, creiamo una versione differente del listato.
Ad esempio prima potremmo scrivere un programma così:
leggi la posta
scrivi a Mario
spegni il pc
e poi il giorno dopo potremmo modificare il codice con:
leggi la posta
scrivi a Mario
scrivi a Luigi
spegni il pc
e infine il giorno dopo ancora:
leggi la posta
scrivi a Pino
scrivi a Luigi
spegni il pc
Come vedete il programma è stato cambiato tre volte, quindi abbiamo fatto tre versioni diverse del nostro codice. Quando si programma è importante mantenere le versioni precedenti del codice. In questo modo possiamo tornare indietro in caso di errori o analizzare le differenze per capire in un attimo dove è il problema o cosa ha migliorato molto il vostro programma. I programmi DIFF servono proprio a questo, a mostrare le differenze. Sembra facile, ma è molto complicato.
Per capire come fare, dobbiamo prima capire che tipo di modifiche possono essere fatte. Per semplicità ci limiteremo alle righe, che è il caso più diffuso.
In un codice le righe possono essere aggiunte o cancellate. Se uno modifica una riga non fa altro che cancellarla e aggiungerne un'altra al suo posto.
Inserire una sola riga di codice, ad esempio, può dividere in due parti il testo; il DIFF deve riconoscere le parti uguali prima e dopo la riga inserita e segnalarci solo la riga aggiunta. Se facessimo un semplice confronto riga per riga, dalla riga aggiunta in poi il DIFF direbbe erroneamente che è tutto diverso.
DIFF sbagliato |
Il DIFF corretto nell'esempio è:
DIFF corretto |
Ora passiamo al difficile, come si può trovare la minima differenza?
Per fortuna parecchie persone hanno lavorato al problema e hanno trovato una soluzione che è spiegata qui: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem .
Riassumendo semplicemente, bisogna costruire una matrice delle sequenza comuni, tipo questa (v. esempio prima):
0 | Leggi | Suona | Scrivi | Spegni | |
0 | 0 | 0 | 0 | 0 | 0 |
Leggi | 0 | 1 | 1 | 1 | 1 |
Scrivi | 0 | 1 | 1 | 2 | 2 |
Spegni | 0 | 1 | 1 | 2 | 3 |
La logica per costruirla e semplice, i titoli delle righe e delle colonne della matrice sono formati da 0 e le righe delle due versioni da analizzare, la prima riga e la prima colonna sono formati da zeri. Poi ogni elemento (Xi,Yi) segue le segue le seguenti regole:
- se il titolo della riga o colonna corrente è zero, la cella contiene zero
- se i titoli di riga e colonna sono diversi inserisco il valore più grande tra le celle (Xi-1,Yi) e (Xi,Yi-1), cioè rispettivamente a sinistra e sopra
- se i titoli di riga e colonna sono uguali inserisco 1 + il valore della cella a sinistra
Se facciamo uno stack con tre campi: versA, versB, uscita; mettiamo le due versioni dell'esempio ripettivamente in versA e verB. Lanciando il seguente codice:
on mouseUp
#trasformiamo i testi in array, dove ogni riga è un elemento dell'array
put field "versA" into tOldSource
put field "versB" into tNewSource
split tOldSource using return
split tNewSource using return
#cerchiamo di capire di quanti elementi, cioè righe, è fatto ogni array
#extents mostra una serie di righe con due numeri, il numero minimo e il massimo per ogni dimensione dell'array
#qui la dimensione è 1
put item 2 of the extents of tOldSource into tOldLineCount
put item 2 of the extents of tNewSource into tNewLineCount
#calcoliamo le varie sequenze con il massimo testo in comune (LCSL)
put CalculateLCSLengths(tOldSource, tNewSource, tOldLineCount, tNewLineCount) into LCLS
#e adesso calcoliamo il DIFF
set the text of field uscita to empty
put tOldLineCount,tNewLineCount into cella
DetermineDiff LCLS, tOldSource, tNewSource, tOldLineCount, tNewLineCount
end mouseUp
function CalculateLCSLengths tOldSource, tNewSource, tOldLineCount, tNewLineCount
#costruiamo la tabella per il calcolo LCSL
#la riga 0 è tutti 0
repeat with tRow = 0 to tOldLineCount
put 0 into pLCSLength[tRow,0]
end repeat
#la colonna 0 è tutti 0
repeat with tCol = 0 to tNewLineCount
put 0 into pLCSLength[0,tCol]
end repeat
#ora calcoliamo le altre varie celle della matrice LCLS
repeat with tRow = 1 to tOldLineCount
repeat with tCol = 1 to tNewLineCount
put tOldSource[tRow] into tOldLine
put tNewSource[tCol] into tNewLine
if tOldLine = tNewLine then
put pLCSLength[tRow - 1,tCol - 1] + 1 into pLCSLength[tRow,tCol]
else
put max(pLCSLength[tRow - 1,tCol],pLCSLength[tRow,tCol - 1]) into pLCSLength[tRow,tCol]
end if
end repeat
end repeat
#finito
return pLCSLength
end CalculateLCSLengths
#abbiamo un messaggio che chiama se stesso parecchie volte
#qui usiamo le @ per risparmiare meomoria ed evitare troppe copie della stessa variabile, però stiamo attenti a non modificarle!
on DetermineDiff @pLCSLength, @pOldSource, @pNewSource, pRow, pCol
put pOldSource[pRow] into tOldLine #la riga nel file originale
put pNewSource[pCol] into tNewLine #la riga nella nuova versione
if pRow > 0 and pCol > 0 and tOldLine = tNewLine then
#sono uguali, andiamo alla cella in alto a sinistra rispetto all'attuale nel LCLS
DetermineDiff pLCSLength, pOldSource, pNewSource, pRow - 1, pCol - 1, "uguale"
#set the text of field uscita to ( field uscita & " " & tab & tOldLine & return)
else if pCol > 0 and (pRow = 0 or pLCSLength[pRow,pCol - 1] >= pLCSLength[pRow - 1, pCol]) then
##caso la cella a sinistra è maggiore o uguale di quella sopra
#è stata aggiunta una riga, moviamoci a sinistra nella tabella LCLS
DetermineDiff pLCSLength, pOldSource, pNewSource, pRow, pCol - 1, "aggiungo"
set the text of field uscita to (field uscita & prow& "+" & tab & tNewLine & return )
else if pRow > 0 and (pCol = 0 or pLCSLength[pRow,pCol - 1] < pLCSLength[pRow - 1, pCol]) then
##caso la cella a sinistra è minore di quella sopra
#è stata rimossa una riga, saliamo di una riga nella tabella LCLS
DetermineDiff pLCSLength, pOldSource, pNewSource, pRow - 1, pCol, "sottraggo"
set the text of field uscita to (field uscita & prow& "-" & tab & tOldLine & return )
end if
end DetermineDiff
otterremo nel campo uscita:
1+ Suona
Che è il DIFF corretto. Cioè alla riga 1 è stato aggiunto "Suona".
Se proviamo con un testo più lungo, ecco cosa succede:
Vers. A | Vers. B | Uscita |
---|---|---|
This part of the document has stayed the same from version to version. It shouldn't be shown if it doesn't change. Otherwise, that would not be helping to compress the size of the changes. This paragraph contains text that is outdated. It will be deleted in the near future. It is important to spell check this dokument. On the other hand, a misspelled word isn't the end of the world. Nothing in the rest of this paragraph needs to be changed. Things can be added after it. | This is an important notice! It should therefore be located at the beginning of this document! This part of the document has stayed the same from version to version. It shouldn't be shown if it doesn't change. Otherwise, that would not be helping to compress anything. It is important to spell check this document. On the other hand, a misspelled word isn't the end of the world. Nothing in the rest of this paragraph needs to be changed. Things can be added after it. This paragraph contains important new additions to this document. | 0+ This is an important 0+ notice! It should 0+ therefore be located at 0+ the beginning of this 0+ document! 0+ 8- compress the size of the 9- changes. 10- 11- This paragraph contains 12- text that is outdated. 13- It will be deleted in the 14- near future. 14+ compress anything. 17- check this dokument. On 17+ check this document. On 24+ 24+ This paragraph contains 24+ important new additions 24+ to this document. |
Corretto ma bruttino da vedere, il DIFF ormai è uno standard che è anche pulito da vedere, quindi cerchiamo di raggruppare le righe consecutive in modo da renderlo più leggibile.
Aggiungendo il seguente codice:
on MouseUp
put the text of field uscita into old
set itemdel to tab
put 0 into cont
repeat for each line tLine in old
put item 1 of tLine into variazione
put char 1 to -2 of variazione into numriga
put char -1 of variazione into tipo # + o -
put item 2 of tLine into riga #il testo della riga
#agg
if tipo = "+" then
if numriga is not oldriga then
put tipo & numriga & CR after nuovo #il testo riformattato
end if
if numriga = oldriga and oldtipo = "-" then
put tipo & numriga & CR after nuovo #il testo riformattato
end if
end if
if tipo = "-" and ((numriga - oldriga) > 1) then
put tipo & numriga & CR after nuovo #il testo riformattato
end if
put riga & CR after nuovo
put tipo into oldtipo
put numriga into oldriga
end repeat
set the text of field uscita to nuovo
end MouseUp
otteniamo:
+0
This is an important
notice! It should
therefore be located at
the beginning of this
document!
-8
compress the size of the
changes.
This paragraph contains
text that is outdated.
It will be deleted in the
near future.
+14
compress anything.
-17
check this dokument. On
+17
check this document. On
+24
This paragraph contains
important new additions
to this document.
Che è proprio come il DIFF usuale, dove il più (+) indica un aggiunta, il meno (-) una cancellazione e il numero dopo il segno a che riga è avvenuta.
Ma ancora siamo a metà del lavoro, nel prossimo posto post vedremo qualche applicazione pratica e utile.
Nessun commento:
Posta un commento