制限されたメモリの最適化


9

2つの文字列間の編集(またはレーベンシュタイン)距離は、1つの文字列を別の文字列に変換するために必要な単一文字の挿入、削除、および置換の最小数です。2つの文字列の長さがそれぞれnである場合、これは動的プログラミングによってO(n ^ 2)時間で実行できることがよく知られています。次のPythonコードは、2つの文字列s1とに対してこの計算を実行します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]

このタスクでは、編集距離の計算にできるだけ近づく必要がありますが、深刻なメモリ制限があります。あなたのコードは1000個の32ビット整数を含む1つの配列を定義することが許可されており、これが計算で使用する唯一の一時的なストレージになります。すべての変数とデータ構造は、この配列に含まれます。特に、長さが1000の文字列の場合、少なくとも1,000,000の数値を格納する必要があるため、上記のアルゴリズムを実装することはできません。言語に32ビット整数が本来備わっていない場合(Pythonなど)、2 ^ 32-1より大きい数を配列に格納しないようにする必要があります。

その部分のメモリ制限を気にすることなく、任意の標準ライブラリを使用してデータを読み込むことができます。コードの主要部分の競争を公平にするために、Cプログラミング言語の操作と機能的に同等の操作のみを使用でき、外部ライブラリを使用できません。

さらに明確にするために、入力データを保存するための、または言語のインタープリター、JVMなどによって使用されるメモリは、制限にカウントされず、ディスクに何も書き込むことができません。入力データがメモリにあるときは読み取り専用であると想定して、作業領域を増やすために再利用できないようにする必要があります。

何を実装する必要がありますか?

コードは、次の形式でファイルを読み取る必要があります。3行になります。最初の行は、実際の編集距離です。2つ目は文字列1、3つ目は文字列2です。https://bpaste.net/show/6905001d52e8のサンプルデータを使用してテストします。文字列の長さは10,000ですが、このデータに特化する必要はありません。2つの文字列間の最小編集距離を出力する必要があります。

また、編集距離が実際に有効な編集セットからのものであることを証明する必要もあります。コードには、メモリを(好きなだけ)使用できるモードに切り替え、編集距離を与える編集操作を出力するスイッチが必要です。

スコア

あなたのスコアはになり(optimal edit distance/divided by the edit distance you find) * 100ます。最初に、2つの文字列間の不一致の数を数えるだけでスコアを取得できることに注意してください。

Linuxへのインストールが簡単で自由に利用できる好きな言語を使用できます。

タイブレーク

タイブレークの場合、私はLinuxマシンでコードを実行し、最速のコードが優先されます。


それはfor(int i=0;i<=5;i++)データを格納しているので許可されiますか?
ベータ崩壊2014

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 2017

回答:


4

C ++、スコア92.35

推定アルゴリズム:このアルゴリズムは、2つの文字列が最初に異なる場所を見つけ、可能なすべてのN操作順列(挿入、削除、置換-一致する文字は操作を消費せずにスキップされます)を試みます。一連の操作が2つの文字列と正常に一致する距離と、文字列の長さが収束する程度に基づいて、可能な操作の各セットにスコアが付けられます。最もスコアの高いN個の操作のセットを決定した後、セット内の最初の操作が適用され、次の不一致が検出され、文字列の最後に到達するまでプロセスが繰り返されます。

プログラムは、1から10までのNのすべての値を試し、最良の結果が得られたレベルを選択します。N = 10は、スコアリング方法が文字列の長さを考慮に入れるので、現在は一般的に最高です。Nの値が高いほど良いでしょうが、指数関数的に時間がかかります。

メモリ使用量:プログラムは完全に反復的であるため、メモリはほとんど必要ありません。プログラムの状態を追跡するために使用される変数は19のみです。これらは#definesによって設定され、グローバル変数として機能します。

使用法:プログラムはfeersumと同じように使用されます。最初のパラメーターはファイルであると見なされ、追加のパラメーターは編集が表示されることを示します。プログラムは常に推定編集距離とスコアを出力します。

検証出力:3行でフォーマットされた検証出力:

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で書いていたのですが、そのとき行を読む機能がないことに気づきました。

最初のコマンドライン引数はファイル名でなければなりません。2番目の引数が存在する場合、編集の要約が出力されます。ファイルの最初の行は無視されますが、2番目と3番目の行は文字列です。

このアルゴリズムは、通常のアルゴリズムの二重にブロックされたバージョンです。これは基本的に同じ数の操作を実行しますが、当然のことながら、それほど正確ではありません。一般的なサブシーケンスがブロックのエッジで分割されると、潜在的な節約の多くが失われるためです。

#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、1つの例にのみ基づいていると仮定して、スコアを計算しました。
feersum 14

これは素晴らしい。はるかに高いスコアを取得することは可能だと思いますか?

3

Python、100

割り当てられたメモリ制限内で編集距離を完全に計算することができました。悲しいことに、このエントリはチャレンジの2つのルールに違反しています。

まず、実際にデータを1000個の32ビット整数に格納していません。10000文字の文字列の場合、私のプログラムは、+ 1、0、または-1のみを含む2つの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.