Найти все, кроме одного совпадения

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

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

Требуется 0,2 с, чтобы заполнить данные 15К на моей машине.

Чтобы скомпилировать его, используйте:

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

Чтобы запустить его, используйте:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

Explaination

Идея проста, для строки, 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] вручную для некоторых простых случаев. В предыдущем коде есть некоторые ошибки, поэтому я обновил пост.
Ray
Спасибо тебе за это. Как вы думаете, может быть решение O (n log n) тоже?
2

C ++

Я пытался придумать хороший алгоритм для этого, но сегодня я немного отвлекся и не мог придумать ничего, что могло бы сработать. Это выполняется за время O (n ^ 3), поэтому очень медленно. Другой вариант, о котором я подумал, мог бы быть теоретически более быстрым, но занял бы пространство O (n ^ 2), а при входных данных 1M он был бы еще хуже.

Это позорно, для входов в 15K требуется 190 секунд. Я постараюсь улучшить это. Редактировать: Добавлена ​​многопроцессорная обработка. Теперь занимает 37 секунд для 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
источник
Мне очень жаль, что это плохое решение. Я искал алгоритм для достижения этой цели в более подходящее время, но пока ничего не нашел ...
rorlork
Что ж, сложность требуемой задачи должна быть от O (n ^ 4) до O (n ^ 5), поэтому долгое время выполнения задано
hoffmale
Я считаю, что это должно быть больше похоже на O (n ^ 3) в худшем случае, по крайней мере, с моим алгоритмом это так. В любом случае, я уверен, что что-то можно сделать, чтобы улучшить его, например, какой-нибудь поиск по дереву, но я не уверен, как это будет реализовано.
rorlork
О да, O (n ^ 3) это ... имел в виду другой подход, который бы взял O (n ^ 4), но этот уже немного бесполезен xD
hoffmale
вы можете сэкономить немного времени, если измените проверку в двух внешних циклах for i < a.length()на i < a.length - (longestA.second - longestA.first)(то же самое для j и b.length ()), поскольку вам не нужно обрабатывать совпадения, меньшие, чем у вашего самого длинного на данный момент
hoffmale
2

р

Кажется, я уже слишком усложнил проблему с предыдущим решением. Это примерно на 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)

Метод:

  • Создайте массив суффиксов для каждой строки
  • Заказать суффиксные массивы
  • Пройдите по каждому из массивов в шахматном порядке, сравнивая начало каждого
MickyT
источник
Конечно, самым простым решением в R является использование Bioconductor.
archaephyrryx
@archaephyrryx Биокондукторный раствор - это весело.
Это было бы ... Но мое быстрое чтение документов было далеко над моей головой. Может быть, если я понял термины :-)
MickyT
Я удалил свой первый комментарий. Конечно, вы можете использовать любую библиотеку с открытым исходным кодом, которая вам нравится.