제한된 메모리 최적화


9

두 문자열 사이 의 편집 (또는 Levenshtein) 거리는 한 문자열을 다른 문자열로 변환하는 데 필요한 최소 단일 문자 삽입, 삭제 및 대체입니다. 2 개의 스트링이 각각 길이 n을 갖는 경우, 이는 동적 프로그래밍에 의해 O (n ^ 2) 시간 내에 수행 될 수있는 것으로 잘 알려져있다. 다음 파이썬 코드는 두 개의 문자열이 계산 수행 s1s2.

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]

이 작업에서는 편집 거리를 계산할 수 있지만 메모리 제한이 심해야합니다. 코드는 1000 개의 32 비트 정수를 포함하는 하나의 배열을 정의 할 수 있으며 이는 계산에 사용하는 유일한 임시 저장소입니다. 모든 변수와 데이터 구조가이 배열에 포함됩니다. 특히 최소 1,000,000 개의 숫자를 저장해야하므로 길이가 1000 인 문자열에 대해 위의 알고리즘을 구현할 수 없습니다. 언어에 자연스럽게 32 비트 정수 (예 : Python)가없는 경우 배열에 2 ^ 32-1보다 큰 숫자를 저장하지 않아야합니다.

해당 부분의 메모리 제한에 대해 걱정하지 않고 선택한 표준 라이브러리를 사용하여 데이터를 읽을 수 있습니다. 코드의 주요 부분에 대해 경쟁을 공평하게하기 위해 C 프로그래밍 언어의 기능과 동등한 기능 만 수행하고 외부 라이브러리를 사용할 수없는 작업 만 사용할 수 있습니다.

더 명확하게 말하면, 입력 데이터를 저장하거나 언어의 인터프리터, JVM 등에서 사용하는 메모리는 한계에 포함되지 않으며 디스크에 아무것도 쓰지 않을 수 있습니다. 더 많은 작업 공간을 확보하기 위해 재사용 할 수 없도록 메모리에있을 때 입력 데이터가 읽기 전용이라고 가정해야합니다.

무엇을 구현해야합니까?

코드는 다음 형식의 파일로 읽어야합니다. 세 줄이 있습니다. 첫 번째 줄은 실제 편집 거리입니다. 두 번째는 문자열 1이고 세 번째는 문자열 2입니다. https://bpaste.net/show/6905001d52e8 의 샘플 데이터를 사용하여 테스트합니다. 여기서 문자열의 길이는 10,000이지만이 데이터에 특화되어서는 안됩니다. 두 문자열 사이에서 찾을 수있는 가장 작은 편집 거리를 출력해야합니다.

또한 편집 거리가 실제로 유효한 편집 세트에서 비롯된 것임을 증명해야합니다. 코드에는 더 많은 메모리를 사용할 수있는 모드로 전환하고 (원하는만큼) 편집 거리를 제공하는 편집 작업을 출력하는 스위치가 있어야합니다.

점수

당신의 점수는 (optimal edit distance/divided by the edit distance you find) * 100입니다. 일을 시작하려면 두 문자열 사이의 불일치 수를 세면 점수를 얻을 수 있습니다.

Linux에서 자유롭게 사용할 수 있고 설치하기 쉬운 원하는 언어를 사용할 수 있습니다.

타이 브레이크

타이-브레이크의 경우 Linux 시스템에서 코드를 실행하면 가장 빠른 코드가 승리합니다.


겠습니까 for(int i=0;i<=5;i++)그것은에 데이터를 저장 있기 때문에 허용 i?
Beta Decay

2
@BetaDecay 네 규칙을 더 면밀히 따르더라도 { uint32_t foo[1000]; for (foo[0] = 0; foo[0] < 5; ++foo[0]) printf("%d ", foo[0]); } 32 비트 정수 배열이 호출된다고 가정합니다 foo.

파일에서 실제 편집 거리를 갖는 지점은 무엇입니까? 프로그램이 실제로 읽어야합니까? 또는 (더 합리적으로 보이는 것) 프로그램이 얼마나 성공적인지 알기위한 것입니까?
feersum

@feersum 정확합니다. 점수가 얼마인지 쉽게 확인할 수 있습니다.

bpaste.net/show/6905001d52e8 는 404 페이지를 제공합니다!
sergiol

답변:


4

C ++, 점수 92.35

추정 알고리즘 : 알고리즘은 두 문자열이 다른 첫 번째 위치를 찾은 다음 가능한 모든 N 연산 순열을 시도합니다 (삽입, 삭제, 바꾸기-일치하는 문자는 작업을 소비하지 않고 건너 뜁니다). 해당 연산 세트가 두 문자열과 얼마나 일치하는지, 그리고 문자열 길이가 수렴되는 정도에 따라 가능한 각 연산 세트의 점수를 매 깁니다. 최고 점수의 N 연산 세트를 결정한 후, 세트의 첫 번째 연산이 적용되고 다음 불일치가 발견되며 문자열의 끝에 도달 할 때까지 프로세스가 반복됩니다.

이 프로그램은 1-10 사이의 모든 N 값을 시도하고 최상의 결과를 제공하는 레벨을 선택합니다. 스코어링 방법이 스트링 길이를 고려하므로 N = 10이 일반적으로 최고입니다. N 값이 높을수록 더 좋을지 모르지만 기하 급수적으로 더 많은 시간이 걸립니다.

메모리 사용 : 프로그램은 순전히 반복적이므로 메모리가 거의 필요하지 않습니다. 프로그램 상태를 추적하는 데 19 개의 변수 만 사용됩니다. 전역 변수로 작동하도록 #defines에 의해 설정됩니다.

사용법 : 프로그램은 feersum과 동일하게 사용됩니다 : 첫 번째 매개 변수는 파일로 가정되며 추가 매개 변수는 편집 내용이 표시되어야 함을 나타냅니다. 프로그램은 항상 예상 편집 거리와 스코어를 인쇄합니다.

검증 출력 : 세 행으로 형식화 된 검증 출력 :

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

맨 위 행은 대상 문자열이고 가운데는 작업이며 아래쪽은 편집중인 문자열입니다. 작업 줄의 공백은 문자가 일치 함을 나타냅니다. 'R'은 편집 문자열에 해당 위치의 문자가 대상 문자열의 문자로 대체되었음을 나타냅니다. 'I'는 편집 문자열에 해당 위치에 대상 문자열의 문자가 삽입되었음을 나타냅니다. 'D'는 편집 문자열에 해당 위치의 문자가 삭제되었음을 나타냅니다. 편집 및 대상 문자열에는 다른 문자열에 문자가 삽입 또는 삭제되어 줄이있을 때 공백이 삽입됩니다.

#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

이 프로그램은 임의의 텍스트 문자열로 작동하도록 설계되었습니다. 13824자를 초과하지 않는 한 길이가 다를 수 있습니다. 1,897 16 비트 정수를 사용하는데 이는 949 32 비트 정수와 같습니다. 처음에는 C로 작성했지만 줄을 읽는 기능이 없다는 것을 깨달았습니다.

첫 번째 명령 줄 인수는 파일 이름이어야합니다. 두 번째 인수가 존재하면 편집 내용의 요약이 인쇄됩니다. 파일의 첫 번째 줄은 무시되고 두 번째와 세 번째는 문자열입니다.

알고리즘은 일반적인 알고리즘의 이중 차단 버전입니다. 기본적으로 동일한 수의 연산을 수행하지만 공통 서브 시퀀스가 ​​블록의 가장자리로 분할되면 잠재적 인 절감 효과가 크게 상실되므로 당연히 정확도가 떨어집니다.

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

첫 답변자가되어 주셔서 감사합니다! 당신의 점수는 얼마입니까?

@Lembik OK, 하나의 예제에만 기반한다고 가정하여 점수를 계산했습니다.
feersum

대단하다. 당신은 훨씬 더 높은 점수를 얻을 수 있다고 생각하십니까?

3

파이썬, 100

할당 된 메모리 제한에서 편집 거리를 완벽하게 계산했습니다. 안타깝게도이 항목은 두 가지 도전 과제 규칙을 위반합니다.

먼저, 실제로 데이터를 1000 32 비트 정수로 저장하지 않았습니다. 10000 자 문자열의 경우, 프로그램은 +1, 0 또는 -1 만 포함하는 두 개의 10000 요소 배열을 만듭니다. 3 진수당 1.585 비트에서 20000 개의 트 라이트를 31700 비트로 압축하여 7 비트의 나머지 16 비트 정수에 300 비트 이상을 남겨 둘 수 있습니다.

둘째, 편집 내용을 표시하는 데 필요한 모드를 구현하지 않았습니다. 대안으로 전체 편집 매트릭스를 인쇄하는 모드를 구현했습니다. 해당 행렬에서 편집 경로를 계산하는 것이 절대적으로 가능하지만 지금 구현할 시간이 없습니다.

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

입력 예 :

2
101100
011010

자세한 출력 예 :

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%
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.