Ограниченная оптимизация памяти


9

Расстояние редактирования (или Левенштейна) между двумя строками - это минимальное количество односимвольных вставок, удалений и замен, необходимых для преобразования одной строки в другую. Если каждая из двух строк имеет длину n, хорошо известно, что это можно сделать за O (n ^ 2) с помощью динамического программирования. Следующий код Python выполняет этот расчет для двух строк 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-битных целых чисел, и это будет единственное временное хранилище, которое вы используете в своих вычислениях. Все переменные и структуры данных должны содержаться в этом массиве. В частности, вы не сможете реализовать описанный выше алгоритм, как для строк длиной 1000, так как для этого потребуется хранить как минимум 1 000 000 чисел. Если ваш язык не имеет 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?
бета-распад

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 операций с наивысшей оценкой применяется первая операция в наборе, обнаруживается следующее несоответствие, и процесс повторяется до тех пор, пока не будет достигнут конец строки.

Программа пробует все значения N от 1 до 10 и выбирает уровень, который дал лучшие результаты. 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 символа. Он использует 1897 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 Хорошо, я рассчитал счет, предполагая, что он основан только на одном примере.
февраля

Это замечательно. Как вы думаете, возможно ли получить гораздо более высокий балл?

3

Питон, 100

Мне удалось точно рассчитать расстояние редактирования в пределах выделенной памяти. К сожалению, эта запись нарушает два правила вызова, по буквам, если не по духу.

Во-первых, я на самом деле не хранил свои данные в 1000 32-битных целых. Для строк по 10000 символов моя программа создает два массива по 10000 элементов, которые будут содержать только +1, 0 или -1. При 1,558 битах на троичное число было бы возможно упаковать эти 20000 тритов в 31700 бит, в результате чего 300 битов было бы более чем достаточно для моих 7 оставшихся 16-битных целых чисел.

Во-вторых, я не реализовал необходимый режим для отображения правок. Я поочередно реализовал режим, который распечатывает полную матрицу редактирования. Абсолютно возможно рассчитать путь редактирования по этой матрице, но у меня сейчас нет времени на его реализацию.

#!/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%
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.