Sınırlı bellek optimizasyonu


9

Düzenlemek (veya Levenshtein) mesafe iki dizeleri arasındaki diğer içine bir dize dönüştürmek için gereken tek karakter yerleştirmeler, silmeler ve değiştirmeler minimal sayıdır. İki dizginin her birinin uzunluğu n ise, bunun dinamik programlama ile O (n ^ 2) zamanında yapılabileceği iyi bilinmektedir. Aşağıdaki Python kodu bu hesaplamayı iki dize s1ve için yapar 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]

Bu görevde, düzenleme mesafesini hesaplamak için olabildiğince yakın olmanız gerekir, ancak ciddi bir bellek kısıtlamasıyla. Kodunuzun 1000 32 bit tamsayı içeren bir dizi tanımlamasına izin verilir ve bu, hesaplamanızda kullandığınız tek geçici depolama alanıdır. Tüm değişkenler ve veri yapıları bu dizide bulunmalıdır. Özellikle, en az 1.000.000 sayı depolamanızı gerektireceği için yukarıdaki algoritmayı 1000 uzunluktaki dizeler için uygulayamazsınız. Dilinizin doğal olarak 32 bit tamsayıları (örneğin Python) olmadığı durumlarda, dizide asla 2 ^ 32-1'den büyük bir sayı kaydetmediğinizden emin olmanız yeterlidir.

Verileri, o bölümdeki bellek kısıtlamaları hakkında endişelenmeden, istediğiniz herhangi bir standart kütüphaneyi kullanarak okuyabilirsiniz. Rekabeti kodunuzun ana bölümü için adil hale getirmek için, yalnızca C programlama dilinde olanlara işlevsel olarak eşdeğer olan ve herhangi bir harici kitaplık kullanamayan işlemleri kullanabilirsiniz.

Daha açık olmak gerekirse, giriş verilerini depolamak veya dilinizin tercümanı, JVM vb. Tarafından kullanılan bellek sınırınıza dahil değildir ve diske bir şey yazamazsınız. Hafızadayken giriş verilerinin salt okunur olduğunu varsaymalısınız, böylece daha fazla çalışma alanı kazanmak için yeniden kullanamazsınız.

Ne uygulamam gerekiyor?

Kodunuz aşağıdaki biçimde bir dosyada okunmalıdır. Üç çizgisi olacak. İlk satır gerçek düzenleme mesafesidir. İkincisi dize 1 ve üçüncüsü dize 2'dir . Dizelerin 10.000 uzunluğuna sahip olduğu https://bpaste.net/show/6905001d52e8 adresindeki örnek verilerle test edeceğim, ancak bu veriler için uzman olmamalı. İki dize arasında bulabileceği en küçük düzenleme mesafesini vermelidir.

Ayrıca, düzenleme mesafenizin gerçekten geçerli bir düzenleme kümesinden geldiğini kanıtlamanız gerekir. Kodunuzda, daha fazla bellek (istediğiniz kadar) kullanabilen bir moda dönüştüren ve düzenleme mesafenizi veren düzenleme işlemlerini sağlayan bir anahtar olmalıdır.

Puan

Skorunuz olacak (optimal edit distance/divided by the edit distance you find) * 100. İşe başlamak için, iki dize arasındaki uyumsuzluk sayısını sayarak bir puan alabileceğinizi unutmayın.

Linux'ta özgürce bulunan ve kurulumu kolay dilediğiniz dilleri kullanabilirsiniz.

Mola, tenis terimi

Bir bağlantı kopması durumunda, kodunuzu Linux makinemde çalıştıracağım ve en hızlı kod kazanacak.


for(int i=0;i<=5;i++)Verileri depoladığı için izin verilir mi i?
Beta Çürüğü

2
Daha yakından kurallarına uymak @BetaDecay Evet rağmen böyle bir şey yapacağını { uint32_t foo[1000]; for (foo[0] = 0; foo[0] < 5; ++foo[0]) printf("%d ", foo[0]); } Bu 32 bitlik tamsayılar diziniz çağrılır üstleniyor foo.

Dosyada gerçek düzenleme mesafesine sahip olmanın anlamı nedir? Programın gerçekten okuması gerekiyor mu? Veya (daha mantıklı görünen) programın ne kadar başarılı olduğunu görmek sizin için orada mı?
feersum

@feersum Kesinlikle. Sadece orada, böylece puanınızın ne olduğunu kolayca görebilirsiniz.

bpaste.net/show/6905001d52e8 bana 404 sayfa veriyor!
sergiol

Yanıtlar:


4

C ++, Puan 92.35

Tahmin Algoritması: Algoritma, iki dizenin farklı olduğu ilk yeri bulur ve sonra tüm olası N işlem permütasyonlarını dener (ekleme, silme, değiştirme - bir işlem tüketmeden atlanan karakterler atlanır). Her olası işlem kümesini, bu işlem kümesinin iki dizeyle ne kadar başarılı bir şekilde eşleştiğine ve dize uzunluklarının ne kadar yakınlaştığına bağlı olarak puanlar. En yüksek skorlu N işlemi kümesi belirlendikten sonra, kümedeki ilk işlem uygulanır, bir sonraki uyumsuzluk bulunur ve işlem dizenin sonuna ulaşılana kadar tekrarlanır.

Program 1-10 arasındaki tüm N değerlerini dener ve en iyi sonuçları veren seviyeyi seçer. N = 10, skorlama yönteminin dize uzunluğunu dikkate almasıyla genellikle en iyisidir. Daha yüksek N değerleri muhtemelen daha iyi olabilir, ancak katlanarak daha fazla zaman alır.

Bellek Kullanımı: Program tamamen yinelemeli olduğundan çok az belleğe ihtiyaç duyar. Program durumunu izlemek için sadece 19 değişken kullanılır. Bunlar, #defines tarafından genel değişkenler olarak işlev görecek şekilde ayarlanır.

Kullanım: Program feersum'larla aynı şekilde kullanılır: ilk parametrenin dosya olduğu varsayılır ve ek parametreler düzenlemelerin gösterilmesi gerektiğini gösterir. Program her zaman tahmini düzenleme mesafesini ve skoru yazdırır.

Doğrulama Çıkışı: Üç satıra biçimlendirdiği doğrulama çıktısı:

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

Üst satır hedef dizedir, orta satır işlemler ve alt satır düzenlenen dizedir. İşlem satırındaki boşluklar karakterlerin eşleştiğini gösterir. 'R', düzenleme dizesinin karakterin bu konumda hedef dizenin karakteri ile değiştirildiğini belirtir. 'I', düzenleme dizesinin bu konuma hedef dizenin karakterine sahip olduğunu gösterir. 'D', düzenleme dizesinin karakterinin o konumda silinmiş olduğunu gösterir. Düzenleme ve hedef dizelerde, diğerinin bir karakteri eklenmiş veya silindiğinde satırlar eklenmiş olarak boşluklar vardır.

#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 keyfi metin dizeleri ile çalışmak üzere tasarlanmıştır. 13824 karakteri geçmediği sürece farklı uzunluklarda olabilirler. 949 32 bit tam sayıya eşdeğer 1,897 16 bit tam sayı kullanır. İlk başta C dilinde yazıyordum, ama sonra bir satır okuma işlevi olmadığını fark ettim.

İlk komut satırı bağımsız değişkeni bir dosya adı olmalıdır. İkinci bir argüman varsa, düzenlemelerin bir özeti yazdırılır. Dosyadaki ilk satır yok sayılırken, ikinci ve üçüncü satır dizelerdir.

Algoritma, alışılmış algoritmanın iki katına çıkmış bir sürümüdür. Temel olarak aynı sayıda işlemi gerçekleştirir, ancak elbette çok daha az doğrudur, çünkü ortak bir alt çizgi bir bloğun kenarına bölünürse, potansiyel tasarrufların çoğu kaybolur.

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

İlk cevap verdiğin için teşekkürler! Skorun nedir?

@Lembik Tamam, sadece bir örneğe dayandığını varsayarak skoru hesapladım.
feersum

Bu harika. Çok daha yüksek bir puan almanın mümkün olduğunu düşünüyor musunuz?

3

Python, 100

Tahsis edilen mesafeyi ayrılan bellek sınırında mükemmel bir şekilde hesaplamayı başardım. Ne yazık ki, bu giriş, ruhsal değilse, mektupta iki zorluğun kurallarını ihlal ediyor.

İlk olarak, verilerimi 1000 adet 32 ​​bitlik inte saklamamıştım. 10000 karakter dizeleri için, programım yalnızca +1, 0 veya -1 içeren iki adet 10000 öğe dizisi oluşturur. Üçlü sayı başına 1.585 bitte, bu 20000 triti 31700 bit'e paketlemek mümkün olacak ve kalan 7 bitlik tam sayılarım için 300 bit yeterli olacak.

İkinci olarak, düzenlemeleri göstermek için gerekli modu uygulamadım. Alternatif olarak, tam düzenleme matrisini yazdıran bir mod uyguladım. Bu matristen düzenleme yolunu hesaplamak kesinlikle mümkündür, ancak şu anda uygulamak için zamanım yok.

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

örnek girdi:

2
101100
011010

örnek ayrıntılı çıktı:

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%
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.