Optimalisasi memori terbatas


9

Jarak edit (atau Levenshtein) antara dua string adalah jumlah minimal penyisipan karakter tunggal, penghapusan dan penggantian yang diperlukan untuk mengubah satu string menjadi yang lain. Jika kedua string memiliki panjang n masing-masing, diketahui bahwa ini dapat dilakukan dalam waktu O (n ^ 2) dengan pemrograman dinamis. Kode Python berikut melakukan perhitungan ini untuk dua string s1dan 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]

Dalam tugas ini Anda harus sedekat mungkin untuk menghitung jarak sunting tetapi dengan batasan memori yang parah. Kode Anda diizinkan untuk mendefinisikan satu array yang berisi 1000 integer 32-bit dan ini adalah satu-satunya penyimpanan sementara yang Anda gunakan dalam perhitungan Anda. Semua variabel dan struktur data harus dimuat dalam array ini. Secara khusus, Anda tidak akan dapat mengimplementasikan algoritma di atas karena untuk string 1000 panjang karena akan mengharuskan Anda untuk menyimpan setidaknya 1.000.000 angka. Di mana bahasa Anda secara alami tidak memiliki bilangan bulat 32 bit (misalnya Python), Anda hanya perlu memastikan bahwa Anda tidak pernah menyimpan angka yang lebih besar dari 2 ^ 32-1 dalam array.

Anda dapat membaca dalam data menggunakan pustaka standar pilihan Anda tanpa khawatir tentang batasan memori di bagian itu. Untuk membuat kompetisi adil untuk bagian utama kode Anda, Anda hanya dapat menggunakan operasi yang secara fungsional setara dengan yang ada dalam bahasa pemrograman C dan tidak dapat menggunakan perpustakaan eksternal.

Agar lebih jelas, memori untuk menyimpan data input atau digunakan oleh penerjemah bahasa Anda, JVM, dll. Tidak diperhitungkan dalam batas Anda dan Anda tidak boleh menulis apa pun ke disk. Anda harus menganggap data input hanya-baca ketika berada di memori sehingga Anda tidak dapat menggunakannya kembali untuk mendapatkan lebih banyak ruang kerja.

Apa yang harus saya terapkan?

Kode Anda harus dibaca dalam file dalam format berikut. Itu akan memiliki tiga baris. Baris pertama adalah jarak sunting yang sebenarnya. Yang kedua adalah string 1 dan yang ketiga adalah string 2. Saya akan mengujinya dengan data sampel di https://bpaste.net/show/6905001d52e8 di mana string memiliki panjang 10.000 tetapi tidak boleh khusus untuk data ini. Seharusnya menampilkan jarak edit terkecil yang dapat ditemukan antara dua string.

Anda juga perlu membuktikan jarak sunting Anda sebenarnya berasal dari serangkaian suntingan yang valid. Kode Anda harus memiliki saklar yang mengubahnya menjadi mode yang dapat menggunakan lebih banyak memori (sebanyak yang Anda suka) dan mengeluarkan operasi edit yang memberikan jarak edit Anda.

Skor

Skor Anda akan menjadi (optimal edit distance/divided by the edit distance you find) * 100. Untuk memulai, perhatikan bahwa Anda bisa mendapatkan skor hanya dengan menghitung jumlah ketidakcocokan antara kedua string.

Anda dapat menggunakan bahasa apa pun yang Anda suka yang tersedia secara bebas dan mudah dipasang di Linux.

Tie break

Dalam kasus tie-break, saya akan menjalankan kode Anda di mesin Linux saya dan kode tercepat menang.


Apakah for(int i=0;i<=5;i++)diizinkan karena menyimpan data i?
Beta Decay

2
@ BetaDecay Ya meskipun untuk mengikuti aturan lebih dekat Anda akan melakukan sesuatu seperti { uint32_t foo[1000]; for (foo[0] = 0; foo[0] < 5; ++foo[0]) printf("%d ", foo[0]); } ini dengan asumsi array 32 bit integer Anda akan dipanggil foo.

Apa gunanya memiliki jarak edit yang sebenarnya dalam file? Apakah program itu seharusnya membacanya? Atau (apa yang tampaknya lebih masuk akal) apakah hanya di sana bagi Anda untuk melihat seberapa sukses program itu?
feersum

@feersum Persis. Itu hanya ada sehingga Anda dapat melihat skor Anda dengan mudah.

bpaste.net/show/6905001d52e8 memberi saya halaman 404!
sergiol

Jawaban:


4

C ++, Skor 92,35

Algoritma Estimasi: Algoritma menemukan tempat pertama dua string berbeda, dan kemudian mencoba semua permutasi operasi N yang mungkin (masukkan, hapus, ganti - karakter yang cocok dilewati tanpa menggunakan operasi). Ini skor setiap set operasi yang mungkin didasarkan pada seberapa jauh lebih jauh bahwa set operasi berhasil cocok dengan dua string, ditambah seberapa banyak hal itu menyebabkan panjang string saling bertemu. Setelah menentukan himpunan tertinggi dari operasi N, operasi pertama dalam himpunan diterapkan, ketidakcocokan berikutnya ditemukan, dan proses berulang sampai akhir string tercapai.

Program mencoba semua nilai N mulai 1-10 dan memilih level yang memberikan hasil terbaik. N = 10 umumnya yang terbaik sekarang karena metode penilaian mempertimbangkan panjang string. Nilai N yang lebih tinggi mungkin akan lebih baik, tetapi membutuhkan waktu yang lebih banyak secara eksponensial.

Penggunaan Memori: Karena program ini murni iteratif, hanya membutuhkan sedikit memori. Hanya 19 variabel yang digunakan untuk melacak status program. Ini ditetapkan oleh #defines untuk bertindak sebagai variabel global.

Penggunaan: Program ini digunakan sama dengan feersum: parameter pertama diasumsikan sebagai file, dan setiap parameter tambahan menunjukkan bahwa pengeditan harus ditampilkan. Program selalu mencetak perkiraan jarak sunting, dan skor.

Output Verifikasi: Output verifikasi yang diformat dalam tiga baris:

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

Baris atas adalah string target, tengah adalah operasi, dan bagian bawah adalah string yang sedang diedit. Spasi pada garis operasi menunjukkan bahwa karakter cocok. 'R' menunjukkan bahwa string edit memiliki karakter di posisi itu diganti dengan karakter string target. 'I' menunjukkan bahwa string edit memasukkan karakter string target pada posisi itu. 'D' menunjukkan bahwa string edit memiliki karakter di posisi itu dihapus. Suntingan edit dan target memiliki ruang yang disisipkan ketika karakter lain disisipkan atau dihapus sehingga mereka berbaris.

#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

Program ini dirancang untuk bekerja dengan string teks yang berubah-ubah. Panjangnya bisa berbeda, asalkan tidak melebihi 13824 karakter. Menggunakan 1.897 bilangan bulat 16-bit, yang setara dengan 949 bilangan bulat 32-bit. Awalnya saya menulisnya dalam C, tetapi kemudian menyadari tidak ada fungsi untuk membaca satu baris.

Argumen baris perintah pertama harus berupa nama file. Jika argumen kedua ada, ringkasan hasil edit dicetak. Baris pertama dalam file diabaikan sementara yang kedua dan ketiga adalah string.

Algoritma adalah versi dua kali lipat dari algoritma yang biasa. Ini pada dasarnya melakukan jumlah operasi yang sama, tetapi tentu saja jauh kurang akurat, karena jika urutan umum terpecah di tepi blok, banyak potensi penghematan hilang.

#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;
}

Terima kasih telah menjadi penjawab pertama! Berapa nilaimu?

@Lembik OK, saya telah menghitung skor, dengan asumsi itu hanya didasarkan pada satu contoh.
feersum

Ini bagus. Apakah Anda pikir mungkin untuk mendapatkan skor yang jauh lebih tinggi?

3

Python, 100

Saya berhasil menghitung jarak edit dengan sempurna dalam batas memori yang diberikan. Sayangnya, entri ini melanggar dua aturan tantangan, dalam surat jika tidak dalam semangat.

Pertama, saya belum benar-benar menyimpan data saya di 1000 int 32-bit. Untuk string 10.000 karakter, program saya membuat dua array 10.000 elemen yang hanya akan berisi +1, 0, atau -1. Pada 1,585 bit per nomor ternary, dimungkinkan untuk mengemas 20000 trit tersebut menjadi 31700 bit, meninggalkan 300 bit lebih dari cukup untuk 7 integer 16-bit saya yang tersisa.

Kedua, saya belum menerapkan mode yang diperlukan untuk menampilkan suntingan. Saya telah, secara bergantian, menerapkan mode yang mencetak matriks edit lengkap. Sangat mungkin untuk menghitung jalur edit dari matriks itu, tetapi saya tidak punya waktu sekarang untuk mengimplementasikannya.

#!/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) + "%"

contoh input:

2
101100
011010

contoh keluaran verbose:

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%
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.