Знаходження всіх, але одного, відповідностей


18

Ця задача полягає у написанні коду для вирішення наступної проблеми.

Враховуючи два рядки A і B, ваш код повинен виводити початкові та кінцеві індекси підрядка A з такими властивостями.

  • Підрядка A також повинна відповідати деякій підрядці B з до однієї підстановки одного символу в рядку.
  • Більше не повинно бути підрядків A, що задовольняє першому властивості.

Наприклад:

A = xxxappleyyyyyyy

B = zapllezzz

Підрядка appleз індексами 4 8(індексування від 1) буде дійсним результатом.

Оцінка

Оцінка вашої відповіді буде сумою довжини вашого коду в байтах + час у секундах, який він займає на моєму комп’ютері під час виконання рядків A і B довжиною по 1 мільйон кожна.

Тестування та введення

Я буду запускати ваш код на двох рядках довжиною 1 мільйон, взятих із рядків у http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

Вхід буде стандартним в і буде просто двома рядками, розділеними новим рядком.

Мови та бібліотеки

Ви можете використовувати будь-яку мову, у якій є доступний компілятор / перекладач / тощо. для Linux та будь-яких бібліотек, які також є відкритим кодом і вільно доступні для Linux.

Моя машина Часи синхронізуються на моїй машині. Це стандартна установка ubuntu на восьмиядерний процесор AMD FX-8350. Це також означає, що мені потрібно мати можливість запускати ваш код. Як наслідок, використовуйте лише доступне безкоштовне програмне забезпечення та додайте повні інструкції щодо компіляції та запуску коду.


Вам потрібно більше абсолютного визначення балів. Тривалість роботи на вашому комп’ютері не здається хорошим методом підрахунку.
mbomb007

7
@ mbomb007 Це єдиний розумний спосіб вимірювання швидкості коду, і це той, який завжди використовується в змаганнях з найшвидшого коду на PPCG! Люди зазвичай публікують свою відповідь на власному комп’ютері у відповіді та чекають, коли ОП потім дасть остаточну оцінку. Це щонайменше на 100% однозначно.

5
@ mbomb007 - це дуже широко використовуваний метод балу для найшвидшого коду.
Оптимізатор

if(hash(str1 == test1 && str2 == test2)) print("100,150") else ..- думки?
Джон Дворак

2
@FryAmTheEggman У дуже неправдоподібному випадку нічия, перша відповідь виграє. appleyпотрібно дві заміни, щоб відповідати apllez. Може, ти пропустив, що це apllв Б, а ні appl?

Відповіді:


4

C ++ час: O (n ^ 2), додатковий простір: O (1)

Для заповнення даних 15К на моїй машині потрібно 0,2 секунди.

Щоб скласти його, використовуйте:

g++ -std=c++11 -O3 code.cpp -o code

Щоб запустити його, використовуйте:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

Пояснення

Ідея проста, для рядка s1і s2ми намагаємося компенсувати з s2допомогою i:

s1: abcabcabc
s2: bcabcab

При зміщенні 3:

s1: abcabcabc
s2:    bcabcab

Потім для кожного зміщення iми виконуємо динамічне сканування програмування на s1[i:]і s2. Для кожного jнехай f[j, 0]буде максимальна довжина dтакою, щоб s1[j - d:j] == s2[j - i - d: j - i]. Аналогічно, нехай f[j, 1]максимальна довжина буде dтакою, щоб рядки s1[j - d:j]і s2[j - i - d:j - i]відрізнялися щонайбільше на 1 символ.

Отже s1[j] == s2[j - i], ми маємо:

f[j, 0] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j]
f[j, 1] = f[j - 1, 1] + 1  // concat solution in f[j - 1, 1] and s1[j]

Інакше:

f[j, 0] = 0  // the only choice is empty string
f[j, 1] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j] (or s2[j - i])

І:

f[-1, 0] = f[-1, 1] = 0 

Оскільки нам потрібно лише f [j - 1,:] для обчислення f [j,:], використовується тільки O (1) додатковий простір.

Нарешті, максимальна довжина буде:

max(f[j, 1] for all valid j and all i)

Код

#include <string>
#include <cassert>
#include <iostream>

using namespace std;

int main() {
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);
    int n1, n2;
    n1 = s1.size();
    n2 = s2.size();
    int max_len = 0;
    int max_end = -1;
    for(int i = 1 - n2; i < n1; i++) {
        int f0, f1;
        int max_len2 = 0;
        int max_end2 = -1;
        f0 = f1 = 0;
        for(int j = max(i, 0), j_end = min(n1, i + n2); j < j_end; j++) {
            if(s1[j] == s2[j - i]) {
                f0 += 1;
                f1 += 1;
            } else {
                f1 = f0 + 1;
                f0 = 0;
            }
            if(f1 > max_len2) {
                max_len2 = f1;
                max_end2 = j + 1;
            }
        }
        if(max_len2 > max_len) {
            max_len = max_len2;
            max_end = max_end2;
        }
    }
    assert(max_end != -1);
    // cout << max_len << endl;
    cout << max_end - max_len + 1 << " " << max_end << endl;
}

Вибачте, я переглянув код і не можу знайти, як він враховує можливість відповідності рядків за винятком одного символу, як у прикладах "apple" та "aplle". Чи можете ви пояснити?
rorlork

@rcrmn Ось чим займається динамічна частина програмування. Щоб зрозуміти, корисно спробувати обчислити f [j, 0] і f [j, 1] вручну для простих випадків. У попередньому коді були деякі помилки, тому я оновив публікацію.
Рей

Дякую за це. Як ви думаєте, може бути рішення O (n log n)?

2

C ++

Я спробував придумати хороший алгоритм, щоб це зробити, але сьогодні я трохи відволікся і не міг придумати нічого, що було б добре. Це працює в O (n ^ 3) час, тому веєєрі повільно. Інший варіант, про який я думав, міг би бути теоретично швидшим, але зайняв би простір O (n ^ 2), а при входах 1М було б навіть гірше.

Це ганебно, на введення 15К потрібно 190 секунд. Я спробую це покращити. Редагування: Додано багатообробну обробку. Тепер потрібно 8 секунд для введення 15K на 8 потоках.

#include <string>
#include <vector>
#include <sstream>
#include <chrono>
#include <thread>
#include <atomic>
#undef cin
#undef cout
#include <iostream>

using namespace std;

typedef pair<int, int> range;

int main(int argc, char ** argv)
{
    string a = "xxxappleyyyyyyy";
    string b = "zapllezzz";

    getline(cin, a);
    getline(cin, b);

    range longestA;
    range longestB;

    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    unsigned cores = thread::hardware_concurrency(); cores = cores > 0 ? cores : 1;

    cout << "Processing on " << cores << " cores." << endl;

    atomic<int> processedCount(0);

    vector<thread> threads;

    range* longestAs = new range[cores];
    range* longestBs = new range[cores];
    for (int t = 0; t < cores; ++t)
    {
        threads.push_back(thread([&processedCount, cores, t, &a, &b, &longestBs, &longestAs]()
        {
            int la = a.length();
            int l = la / cores + (t==cores-1? la % cores : 0);
            int lb = b.length();
            int aS = t*(la/cores);

            for (int i = aS; i < aS + l; ++i)
            {
                int count = processedCount.fetch_add(1);
                if ((count+1) * 100 / la > count * 100 / la)
                {
                    cout << (count+1) * 100 / la << "%" << endl;
                }
                for (int j = 0; j < lb; ++j)
                {
                    range currentB = make_pair(j, j);
                    bool letterChanged = false;
                    for (int k = 0; k + j < lb && k + i < la; ++k)
                    {
                        if (a[i + k] == b[j + k])
                        {
                            currentB = make_pair(j, j + k);
                        }
                        else if (!letterChanged)
                        {
                            letterChanged = true;
                            currentB = make_pair(j, j + k);
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (currentB.second - currentB.first > longestBs[t].second - longestBs[t].first)
                    {
                        longestBs[t] = currentB;
                        longestAs[t] = make_pair(i, i + currentB.second - currentB.first);
                    }
                }
            }
        }));
    }

    longestA = make_pair(0,0);
    for(int t = 0; t < cores; ++t)
    {
        threads[t].join();

        if (longestAs[t].second - longestAs[t].first > longestA.second - longestA.first)
        {
            longestA = longestAs[t];
            longestB = longestBs[t];
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    cout << "First substring at range (" << longestA.first << ", " << longestA.second << "):" << endl;
    cout << a.substr(longestA.first, longestA.second - longestA.first + 1) << endl;
    cout << "Second substring at range (" << longestB.first << ", " << longestB.second << "):" << endl;
    cout << b.substr(longestB.first, longestB.second - longestB.first + 1) << endl;
    cout << "It took me " << time_span.count() << " seconds for input lengths " << a.length() << " and " << b.length() <<"." << endl;

    char c;
    cin >> c;
    return 0;
}

Мені дуже шкода, що це таке погане рішення. Я шукав алгоритм, щоб досягти цього в кращий час, але поки нічого не знайшов ...
rorlork

Ну, складність необхідного завдання повинна бути приблизно від O (n ^ 4) до O (n ^ 5), тому даються тривалі часи
hoffmale

Я вважаю, що він повинен бути більше схожим на O (n ^ 3) в гіршому випадку, принаймні, з мого алгоритму. У будь-якому випадку, я впевнений, що щось можна зробити для його вдосконалення, як-от якийсь пошук дерев, але я не впевнений, як це було б реалізовано.
rorlork

О так, O (n ^ 3), це ... мав інший підхід на увазі, який би сприйняв O (n ^ 4), але цей вид на сьогоднішній день є марним xD
hoffmale

Ви можете зекономити невелику кількість часу, якщо змінити прапорець у двох зовнішніх для циклів з i < a.length()на i < a.length - (longestA.second - longestA.first)(те ж саме для j та b.length ()), оскільки вам не потрібно буде обробляти будь-які збіги, менші за ваш найдовший
hoffmale

2

R

Здається, я перестала ускладнювати проблему попереднім рішенням. Це приблизно на 50% швидше (23 секунди на 15k рядках), ніж попереднє, і досить просте.

rm(list=ls(all=TRUE))
a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
matchLen=1
matchIndex=1
indexA = 1
repeat {    
    i = 0
    repeat {
        srch = substring(a,indexA,indexA+matchLen+i)
        if (agrepl(srch,b,max.distance=list(insertions=0,deletions=0,substitutions=1)))
            i = i + 1
        else {
            if (i > 0) {
                matchLen = matchLen + i - 1
                matchIndex = indexA
            }
            break
        }
    }
    indexA=indexA+1
    if (indexA + matchLen > nchar(a)) break
}
c(matchIndex, matchLen + matchIndex)
print (substring(a,matchIndex, matchLen + matchIndex))
print(proc.time()-s)

Це ніколи не буде претендентом через мову, але мені це було весело, роблячи це.
Не впевнений у його складності, але за пару ~ 15k рядків потрібно 43 секунди, використовуючи одну нитку. Найбільша частина цього становила сортування масивів. Я спробував деякі інші бібліотеки, але без істотного вдосконалення.

a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
N=nchar
S=substring
U=unlist
V=strsplit
A=N(a)
B=N(b)
a=S(a,1:A)
b=S(b,1:B)
a=sort(a,method="quick")
b=sort(b,method="quick")
print(proc.time()-s)
C=D=1
E=X=Y=I=0
repeat{
    if(N(a[C])>E && N(b[D])>E){
        for(i in E:min(N(a[C]),N(b[D]))){
            if (sum(U(V(S(a[C],1,i),''))==U(V(S(b[D],1,i),'')))>i-2){
                F=i
            } else break
        }
        if (F>E) {
            X=A-N(a[C])+1
            Y=X+F-1
            E=F
        }
        if (a[C]<b[D])
            C=C+1
            else
            D=D+1
    } else
        if(S(a[C],1,1)<S(b[D],1,1))C=C+1 else D=D+1
    if(C>A||D>B)break
}
c(X,Y)
print(proc.time()-s)

Спосіб:

  • Створіть суфіксний масив для кожного рядка
  • Упорядкуйте масиви суфіксів
  • Перегляньте кожен масив поетапно, порівнявши початок кожного

Звичайно, найпростішим рішенням R є використання біокондуктора.
архефірикс

@archaephyrryx Рішення біопровідника було б цікаво.

Було б ... Але моє швидке читання документів перебігло мені голову. Можливо, якби я зрозумів терміни :-)
MickyT

Я видалив свій перший коментар. Звичайно, ви можете використовувати будь-яку бібліотеку з відкритим кодом, яка вам подобається, для цього завдання.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.