Ottimizzazione della memoria limitata


9

La distanza di modifica (o Levenshtein) tra due stringhe è il numero minimo di inserimenti, eliminazioni e sostituzioni di caratteri singoli necessari per trasformare una stringa nell'altra. Se le due stringhe hanno lunghezza n ciascuna, è noto che ciò può essere fatto in tempo O (n ^ 2) mediante la programmazione dinamica. Il seguente codice Python esegue questo calcolo per due stringhe s1e s2.

def edit_distance(s1, s2):
    l1 = len(s1)
    l2 = len(s2)

    matrix = [range(l1 + 1)] * (l2 + 1)
    for zz in range(l2 + 1):
      matrix[zz] = range(zz,zz + l1 + 1)
    for zz in range(0,l2):
      for sz in range(0,l1):
        if s1[sz] == s2[zz]:
          matrix[zz+1][sz+1] = min(matrix[zz+1][sz] + 1, matrix[zz][sz+1] + 1, matrix[zz][sz])
        else:
          matrix[zz+1][sz+1] = min(matrix[zz+1][sz] + 1, matrix[zz][sz+1] + 1, matrix[zz][sz] + 1)
    return matrix[l2][l1]

In questo compito devi avvicinarti il ​​più possibile al calcolo della distanza di modifica ma con una severa restrizione della memoria. Il tuo codice è autorizzato a definire un array contenente 1000 numeri interi a 32 bit e questo deve essere l'unico archivio temporaneo che usi nel tuo calcolo. Tutte le variabili e le strutture di dati devono essere contenute in questo array. In particolare, non saresti in grado di implementare l'algoritmo sopra per stringhe di lunghezza 1000 in quanto richiederebbe di memorizzare almeno 1.000.000 di numeri. Laddove la tua lingua non abbia naturalmente numeri interi a 32 bit (ad esempio Python) devi semplicemente assicurarti di non memorizzare mai un numero maggiore di 2 ^ 32-1 nell'array.

Puoi leggere i dati utilizzando qualsiasi libreria standard di tua scelta senza preoccuparti delle restrizioni di memoria in quella parte. Al fine di rendere la competizione leale per la parte principale del codice, è possibile utilizzare solo operazioni funzionalmente equivalenti a quelle del linguaggio di programmazione C e non utilizzare librerie esterne.

Per essere più chiari, la memoria per archiviare i dati di input o utilizzati dall'interprete della tua lingua, JVM ecc. Non conta ai fini del tuo limite e potresti non scrivere nulla sul disco. È necessario supporre che i dati di input siano di sola lettura quando sono in memoria, quindi non è possibile riutilizzarli per guadagnare più spazio di lavoro.

Cosa devo implementare?

Il codice dovrebbe essere letto in un file nel seguente formato. Avrà tre righe. La prima riga è la vera distanza di modifica. Il secondo è la stringa 1 e il terzo è la stringa 2. Lo testerò con i dati di esempio su https://bpaste.net/show/6905001d52e8 dove le stringhe hanno lunghezza 10.000 ma non dovrebbero essere specializzate per questi dati. Dovrebbe generare la minima distanza di modifica che può trovare tra le due stringhe.

Dovrai anche dimostrare che la distanza di modifica proviene effettivamente da una serie valida di modifiche. Il tuo codice dovrebbe avere un interruttore che lo trasforma in una modalità che può usare più memoria (quanto vuoi) e produce le operazioni di modifica che danno la distanza di modifica.

Punto

Il tuo punteggio sarà il (optimal edit distance/divided by the edit distance you find) * 100. Per iniziare, nota che puoi ottenere un punteggio semplicemente contando il numero di disallineamenti tra le due stringhe.

Puoi usare qualsiasi lingua che ti piace che è liberamente disponibile e facile da installare in Linux.

Pausa

In caso di tie-break, eseguirò il tuo codice sulla mia macchina Linux e il codice più veloce vince.


Sarebbe for(int i=0;i<=5;i++)permesso perché sta archiviando i dati i?
Decadimento beta

2
@BetaDecay Sì, anche se per seguire le regole più da vicino faresti qualcosa del genere. Supponiamo { uint32_t foo[1000]; for (foo[0] = 0; foo[0] < 5; ++foo[0]) printf("%d ", foo[0]); } che verrà chiamato il tuo array di numeri interi a 32 bit foo.

Qual è il punto di avere la vera distanza di modifica nel file? Il programma dovrebbe davvero leggerlo? Oppure (cosa sembra più sensato) è lì dentro per te vedere quanto successo ha avuto il programma?
feersum

@feersum Exactly. È proprio lì, così puoi vedere quale è il tuo punteggio facilmente.

bpaste.net/show/6905001d52e8 mi dà una pagina 404!
sergiol

Risposte:


4

C ++, punteggio 92.35

Algoritmo di stima: l'algoritmo trova il primo posto in cui le due stringhe differiscono, quindi prova tutte le possibili permutazioni di operazioni N (inserisci, cancella, sostituisci - i caratteri che corrispondono vengono saltati senza consumare un'operazione). Segna ogni possibile insieme di operazioni in base a quanto più lontano tale insieme di operazioni corrisponde correttamente alle due stringhe, oltre a quanto provoca la convergenza delle lunghezze delle stringhe. Dopo aver determinato il set di operazioni N con il punteggio più alto, viene applicata la prima operazione nel set, viene trovata la mancata corrispondenza successiva e il processo si ripete fino al raggiungimento della fine della stringa.

Il programma prova tutti i valori di N da 1 a 10 e seleziona il livello che ha dato i risultati migliori. N = 10 è generalmente il migliore ora che il metodo di calcolo del punteggio prende in considerazione la lunghezza della stringa. Valori più alti di N sarebbero probabilmente anche migliori, ma richiederebbero in modo esponenziale più tempo.

Utilizzo della memoria: poiché il programma è puramente iterativo, richiede pochissima memoria. Solo 19 variabili vengono utilizzate per tenere traccia dello stato del programma. Questi sono impostati da #define per agire come variabili globali.

Utilizzo: il programma è usato come quello di feersum: si presume che il primo parametro sia il file e tutti i parametri aggiuntivi indicano che le modifiche devono essere visualizzate. Il programma stampa sempre la distanza di modifica stimata e il punteggio.

Output di verifica: output di verifica formattato in tre righe:

11011111100101100111100110100 110 0 0000   0 01101
R I          IR     R        D   D D    DDD D     D
01 1111110010 0001110001101000110101000011101011010

La riga superiore è la stringa di destinazione, la metà è le operazioni e la parte inferiore è la stringa che viene modificata. Gli spazi nella riga operativa indicano che i caratteri corrispondono. 'R' indica che la stringa di modifica ha il suo carattere in quella posizione sostituito con il carattere della stringa di destinazione. 'I' indica che la stringa di modifica ha il carattere della stringa di destinazione inserito in quella posizione. 'D' indica che la stringa di modifica ha il carattere in quella posizione cancellato. Le stringhe di modifica e destinazione hanno spazi inseriti quando l'altro ha un carattere inserito o eliminato, quindi si allineano.

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <math.h>
#include <fstream>

int memory[1000];
#define first (*(const char **)&memory[0])
#define second (*(const char **)&memory[1])
#define block_ia memory[2]
#define block_ib memory[3]
#define block_n memory[4]
#define block_op memory[5]
#define block_o memory[6]
#define block_x memory[7]
#define n memory[8]
#define opmax memory[9]
#define best_op memory[10]
#define best_score memory[11]
#define score memory[12]
#define best_counter memory[13]
#define la memory[14]
#define lb memory[15]
#define best memory[16]
#define bestn memory[17]
#define total memory[18]

// verification variables
char printline1[0xffff]={};
char *p1=printline1;
char printline2[0xffff]={};
char *p2=printline2;
char printline3[0xffff]={};
char *p3=printline3;


// determine how many characters match after a set of operations
int block(){
    block_ia=0;
    block_ib=0;
    for ( block_x=0;block_x<block_n;block_x++){
        block_o = block_op%3;
        block_op /= 3;
        if ( block_o == 0 ){ // replace
            block_ia++;
            block_ib++;
        } else if ( block_o == 1 ){ // delete
            block_ib++;
        } else { // insert
            if ( first[block_ia] ){ 
                block_ia++;
            }
        }
        while ( first[block_ia] && first[block_ia]==second[block_ib] ){ // find next mismatch
            block_ia++;
            block_ib++;
        }
        if ( first[block_ia]==0 ){
            return block_x;
        }
    }
    return block_n;
}

// find the highest-scoring set of N operations for the current string position
void bestblock(){
    best_op=0;
    best_score=0;
    la = strlen(first);
    lb = strlen(second);
    block_n = n;
    for(best_counter=0;best_counter<opmax;best_counter++){
        block_op=best_counter;
        score = n-block();
        score += block_ia-abs((la-block_ia)-(lb-block_ib));
        if ( score > best_score ){
            best_score = score;
            best_op = best_counter;
        }
    }
}

// prepare edit confirmation record
void printedit(const char * a, const char * b, int o){
    o%=3;
    if ( o == 0 ){ // replace
        *p1 = *a;
        if ( *b ){
            *p2 = 'R';
            *p3 = *b;
            b++;
        } else {
            *p2 = 'I';
            *p3 = ' ';
        }
        a++;
    } else if ( o == 1 ){ // delete
        *p1 = ' ';
        *p2 = 'D';
        *p3 = *b;
        b++;
    } else { // insert
        *p1 = *a;
        *p2 = 'I';
        *p3 = ' ';
        a++;
    }
    p1++;
    p2++;
    p3++;
    while ( *a && *a==*b ){
        *p1 = *a;
        *p2 = ' ';
        *p3 = *b;
        p1++;
        p2++;
        p3++;
        a++;
        b++;
    }
}


int main(int argc, char * argv[]){

    if ( argc < 2 ){
        printf("No file name specified\n");
        return 0;
    }

    std::ifstream file(argv[1]);
    std::string line0,line1,line2;
    std::getline(file,line0);
    std::getline(file,line1);
    std::getline(file,line2);

    // begin estimating Levenshtein distance
    best = 0;
    bestn = 0;
    for ( n=1;n<=10;n++){ // n is the number of operations that can be in a test set
        opmax = (int)pow(3.0,n);
        first = line1.c_str();
        second = line2.c_str();
        while ( *first && *first == *second ){
            first++;
            second++;
        }
        total=0;
        while ( *first && *second ){
            bestblock();
            block_n=1;
            block_op=best_op;
            block();
            total ++;
            first += block_ia;
            second += block_ib;
        }
        // when one string is exhausted, all following ops must be insert or delete
        while(*second){
            total++;
            second++;
        }
        while(*first){
            total++;
            first++;
        }
        if ( !best || total < best ){
            best = total;
            bestn = n;
        }
    }
    // done estimating Levenshtein distance

    // dump info to prove the edit distance actually comes from a valid set of edits
    if ( argc >= 3 ){
        p1 = printline1;
        p2 = printline2;
        p3 = printline3;
        n = bestn;
        opmax = (int)pow(3.0,n);
        first = line1.c_str();
        second = line2.c_str();
        while ( *first && *first == *second ){
            *p1 = *first;
            *p2 = ' ';
            *p3 = *second;
            p1++;
            p2++;
            p3++;
            first++;
            second++;
        }
        while ( *first && *second){
            bestblock();
            block_n=1;
            block_op=best_op;
            block();
            printedit(first,second,best_op);
            first += block_ia;
            second += block_ib;
        }
        while(*second){
            *p1=' ';
            *p2='D';
            *p3=*second;
            p1++;
            p2++;
            p3++;
            second++;
        }
        while(*first){
            *p1=*first;
            *p2='I';
            *p3=' ';
            p1++;
            p2++;
            p3++;
            first++;
        }

        p1 = printline1;
        p2 = printline2;
        p3 = printline3;
        int ins=0;
        int del=0;
        int rep=0;
        while ( *p1 ){
            int a;
            for ( a=0;a<79&&p1[a];a++)
                printf("%c",p1[a]);
            printf("\n");
            p1+=a;
            for ( a=0;a<79&&p2[a];a++){
                ins += ( p2[a] == 'I' );
                del += ( p2[a] == 'D' );
                rep += ( p2[a] == 'R' );
                printf("%c",p2[a]);
            }
            printf("\n");
            p2+=a;
            for ( a=0;a<79&&p3[a];a++)
                printf("%c",p3[a]);
            printf("\n\n");
            p3+=a;
        }
        printf("Best N=%d\n",bestn);
        printf("Inserted = %d, Deleted = %d, Replaced=%d, Total = %d\nLength(line1)=%d, Length(Line2)+ins-del=%d\n",ins,del,rep,ins+del+rep,line1.length(),line2.length()+ins-del);
    }

    printf("%d, Score = %0.2f\n",best,2886*100.0/best);
    system("pause");
    return 0;
}

7

C ++ 75.0

Il programma è progettato per funzionare con stringhe di testo arbitrarie. Possono avere lunghezze diverse purché non superino i 13824 caratteri. Utilizza 1.897 numeri interi a 16 bit, equivalenti a 949 numeri interi a 32 bit. All'inizio lo stavo scrivendo in C, ma poi mi sono reso conto che non c'era alcuna funzione per leggere una riga.

Il primo argomento della riga di comando dovrebbe essere un nome file. Se esiste un secondo argomento, viene stampato un riepilogo delle modifiche. La prima riga nel file viene ignorata mentre la seconda e la terza sono le stringhe.

L'algoritmo è una versione doppiamente bloccata del solito algoritmo. Esegue sostanzialmente lo stesso numero di operazioni, ma ovviamente è molto meno preciso, poiché se una sottosequenza comune viene suddivisa sul bordo di un blocco, molti dei potenziali risparmi vengono persi.

#include <cstring>
#include <inttypes.h>
#include <iostream>
#include <fstream>

#define M 24
#define MAXLEN (M*M*M)
#define SETMIN(V, X) if( (X) < (V) ) { (V) = (X); }
#define MIN(X, Y) ( (X) < (Y) ? (X) : (Y) )

char A[MAXLEN+1], B[MAXLEN+1];
uint16_t d0[M+1][M+1], d1[M+1][M+1], d2[M+1][M+1];

int main(int argc, char**argv)
{

    if(argc < 2)
        return 1;

    std::ifstream fi(argv[1]);

    std::string Astr, Bstr;
    for(int i = 3; i--;)
        getline(fi, i?Bstr:Astr);
    if(!fi.good()) {
        printf("Error reading file");
        return 5;
    }
    if(Astr.length() > MAXLEN || Bstr.length() > MAXLEN) {
        printf("String too long");
        return 7;
    }

    strcpy(A, Astr.c_str());
    strcpy(B, Bstr.c_str());

    uint16_t lA = Astr.length(), lB = Bstr.length();
    if(!lA || !lB) {
        printf("%d\n", lA|lB);
        return 0;
    }
    uint16_t nbA2, nbB2, bA2, bB2, nbA1, nbB1, bA1, bB1, nbA0, nbB0, bA0, bB0; //block, number of blocks
    uint16_t iA2, iB2, iA1, iB1, jA2, jB2, jA1, jB1; //start, end indices of block

    nbA2 = MIN(M, lA);
    nbB2 = MIN(M, lB);
    for(bA2 = 0; bA2 <= nbA2; bA2++) {
        iA2 = lA * (bA2-1)/nbA2,  jA2 = lA * bA2/nbA2;
        for(bB2 = 0; bB2 <= nbB2; bB2++) {
            if(!(bA2|bB2)) {
                d2[0][0] = 0;
                continue;
            }
            iB2 = lB * (bB2-1)/nbB2,  jB2 = lB * bB2/nbB2;
            d2[bA2][bB2] = ~0;
            if(bB2)
                SETMIN(d2[bA2][bB2], d2[bA2][bB2-1] + (jB2-iB2));
            if(bA2)
                SETMIN(d2[bA2][bB2], d2[bA2-1][bB2] + (jA2-iA2));

            if(bA2 && bB2) {
                nbA1 = MIN(M, jA2-iA2);
                nbB1 = MIN(M, jB2-iB2);
                for(bA1 = 0; bA1 <= nbA1; bA1++) {
                    iA1 = iA2 + (jA2-iA2) * (bA1-1)/nbA1, jA1 = iA2 + (jA2-iA2) * bA1/nbA1;
                    for(bB1 = 0; bB1 <= nbB1; bB1++) {
                        if(!(bA1|bB1)) {
                            d1[0][0] = 0;
                            continue;
                        }
                        iB1 = iB2 + (jB2-iB2) * (bB1-1)/nbB1, jB1 = iB2 + (jB2-iB2) * bB1/nbB1;
                        d1[bA1][bB1] = ~0;
                        if(bB1)
                            SETMIN(d1[bA1][bB1], d1[bA1][bB1-1] + (jB1-iB1));
                        if(bA1)
                            SETMIN(d1[bA1][bB1], d1[bA1-1][bB1] + (jA1-iA1));

                        if(bA1 && bB1) {
                            nbA0 = jA1-iA1;
                            nbB0 = jB1-iB1;
                            for(bA0 = 0; bA0 <= nbA0; bA0++) {
                                for(bB0 = 0; bB0 <= nbB0; bB0++) {
                                    if(!(bA0|bB0)) {
                                        d0[0][0] = 0;
                                        continue;
                                    }
                                    d0[bA0][bB0] = ~0;
                                    if(bB0)
                                        SETMIN(d0[bA0][bB0], d0[bA0][bB0-1] + 1);
                                    if(bA0)
                                        SETMIN(d0[bA0][bB0], d0[bA0-1][bB0] + 1);
                                    if(bA0 && bB0)
                                        SETMIN(d0[bA0][bB0], d0[bA0-1][bB0-1] + (A[iA1 + nbA0 - 1] != B[iB1 + nbB0 - 1]));
                                }
                            }
                            SETMIN(d1[bA1][bB1], d1[bA1-1][bB1-1] + d0[nbA0][nbB0]);
                        }
                    }
                }

                SETMIN(d2[bA2][bB2], d2[bA2-1][bB2-1] + d1[nbA1][nbB1]);
            }
        }
    }
    printf("%d\n", d2[nbA2][nbB2]);

    if(argc == 2)
        return 0;

    int changecost, total = 0;
    for(bA2 = nbA2, bB2 = nbB2; bA2||bB2; ) {
        iA2 = lA * (bA2-1)/nbA2,  jA2 = lA * bA2/nbA2;
        iB2 = lB * (bB2-1)/nbB2,  jB2 = lB * bB2/nbB2;
        if(bB2 && d2[bA2][bB2-1] + (jB2-iB2) == d2[bA2][bB2]) {
            total += changecost = (jB2-iB2);
            char tmp = B[jB2];
            B[jB2] = 0;
            printf("%d %d deleted {%s}\n", changecost, total, B + iB2);
            B[jB2] = tmp;
            --bB2;
        } else if(bA2 && d2[bA2-1][bB2] + (jA2-iA2) == d2[bA2][bB2]) {
            total += changecost = (jA2-iA2);
            char tmp = B[jA2];
            A[jA2] = 0;
            printf("%d %d inserted {%s}\n", changecost, total, A + iA2);
            A[jA2] = tmp;
            --bA2;
        } else {
            total += changecost = d2[bA2][bB2] - d2[bA2-1][bB2-1];
            char tmpa = A[jA2], tmpb = B[jB2];
            B[jB2] = A[jA2] = 0;
            printf("%d %d changed {%s} to {%s}\n", changecost, total, B + iB2, A + iA2);
            A[jA2] = tmpa, B[jB2] = tmpb;
            --bA2, --bB2;
        }
    }


    return 0;
}

Grazie per essere il primo risponditore! Qual è il tuo punteggio?

@Lembik OK, ho calcolato il punteggio, supponendo che si basi solo su un esempio.
feersum,

Questo è fantastico Pensi che sia possibile ottenere un punteggio molto più alto?

3

Python, 100

Sono riuscito a calcolare perfettamente la distanza di modifica nel limite di memoria assegnato. Purtroppo, questa voce viola due regole della sfida, nella lettera se non nello spirito.

Innanzitutto, in realtà non ho archiviato i miei dati in 1000 32 bit. Per stringhe di 10000 caratteri, il mio programma crea due array di 10000 elementi che conterranno solo +1, 0 o -1. A 1,585 bit per numero ternario, sarebbe possibile comprimere quei 20000 trits in 31700 bit, lasciando 300 bit più che sufficienti per i miei 7 interi rimanenti a 16 bit.

In secondo luogo, non ho implementato la modalità richiesta per mostrare le modifiche. In alternativa, ho implementato una modalità che stampa l'intera matrice di modifica. È assolutamente possibile calcolare il percorso di modifica da quella matrice, ma al momento non ho tempo per implementarlo.

#!/usr/bin/env python

import sys

# algorithm originally from
# https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows

print_rows = False
if len(sys.argv) > 2:
    print_rows = True

def LevenshteinDistance(s, t):
    # degenerate cases
    if s == t:
        return 0
    if len(s) == 0:
        return len(t)
    if len(t) == 0:
        return len(s)

    # create two work vectors of integer distance deltas

    # these lists will only ever contain +1, 0, or -1
    # so they COULD be packed into 1.585 bits each
    # 15850 bits per list, 31700 bits total, leaving 300 bits for all the other variables

    # d0 is the previous row
    # initialized to 0111111... which represents 0123456...
    d0 = [1 for i in range(len(t)+1)]
    d0[0] = 0        
    if print_rows:
        row = ""
        for i in range(len(t)+1):
            row += str(i) + ", "
        print row

    # d1 is the row being calculated
    d1 = [0 for i in range(len(t)+1)]

    for i in range(len(s)-1):
        # cummulative values of cells north, west, and northwest of the current cell
        left = i+1
        upleft = i
        up = i+d0[0]
        if print_rows:
            row = str(left) + ", "
        for j in range(len(t)):
            left += d1[j]
            up += d0[j+1]
            upleft += d0[j]
            cost = 0 if (s[i] == t[j]) else 1
            d1[j + 1] = min(left + 1, up + 1, upleft + cost) - left
            if print_rows:
                row += str(left+d1[j+1]) + ", "

        if print_rows:
            print row

        for c in range(len(d0)):
            d0[c] = d1[c]

    return left+d1[j+1]

with open(sys.argv[1]) as f:
    lines = f.readlines()

perfect = lines[0]
string1 = lines[1]
string2 = lines[2]
distance = LevenshteinDistance(string1,string2)
print "edit distance: " + str(distance)
print "score: " + str(int(perfect)*100/distance) + "%"

esempio di input:

2
101100
011010

esempio di output dettagliato:

0, 1, 2, 3, 4, 5, 6,
1, 1, 1, 2, 3, 4, 5,
2, 1, 2, 2, 2, 3, 4,
3, 2, 1, 2, 3, 2, 3,
4, 3, 2, 1, 2, 3, 3,
5, 4, 3, 2, 1, 2, 3,
6, 5, 4, 3, 2, 2, 2,
edit distance: 2
score: 100%
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.