Localizando correspondências com tudo menos um

18

Esse desafio é escrever código para resolver o seguinte problema.

Dadas duas seqüências A e B, seu código deve gerar os índices inicial e final de uma subseqüência de caracteres A com as seguintes propriedades.

  • A substring de A também deve corresponder a alguma substring de B com até uma substituição de um único caractere na string.
  • Não deve mais haver substring de A que satisfaça a primeira propriedade.

Por exemplo:

A = xxxappleyyyyyyy

B = zapllezzz

A substring applecom índices 4 8(indexação de 1) seria uma saída válida.

Ponto

A pontuação da sua resposta será a soma do comprimento do seu código em bytes + o tempo em segundos que leva no meu computador quando executado nas cadeias A e B de 1 milhão de comprimento cada.

Teste e entrada

Executarei seu código em duas cadeias de comprimento 1 milhão extraídas das cadeias em http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

A entrada será no padrão e será simplesmente duas strings, separadas por uma nova linha.

Línguas e bibliotecas

Você pode usar qualquer idioma que tenha um compilador / intérprete disponível gratuitamente / etc. para Linux e quaisquer bibliotecas também de código aberto e disponíveis gratuitamente para Linux.

Minha máquina Os horários serão executados na minha máquina. Esta é uma instalação padrão do ubuntu em um processador AMD FX-8350 de oito núcleos. Isso também significa que eu preciso poder executar seu código. Como conseqüência, use apenas software livre facilmente disponível e inclua instruções completas sobre como compilar e executar seu código.

isaacg
fonte
Você precisa de uma definição de pontuação mais absoluta. O tempo de execução no seu computador não soa como um bom método de pontuação.
mbomb007
7
@ mbomb007 É a única maneira sensata de medir a velocidade do código e é sempre usada nas competições de código mais rápidas no PPCG! As pessoas normalmente publicam sua pontuação em seu próprio computador em suas respostas e esperam que o OP produza uma pontuação definitiva. É 100% inequívoco, pelo menos.
5
@ mbomb007 que é um método de pontuação muito usado para o código mais rápido.
Optimizer
if(hash(str1 == test1 && str2 == test2)) print("100,150") else ..-- pensamentos?
John Dvorak
2
@FryAmTheEggman No caso muito improvável de empate, a primeira resposta vence. appleyprecisa de duas substituições para combinar apllez. Talvez você tenha perdido que está apllem B e não appl?

Respostas:

4

Tempo C ++: O (n ^ 2), espaço extra: O (1)

Demora 0,2s para concluir os dados de 15K na minha máquina.

Para compilá-lo, use:

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

Para executá-lo, use:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

Explicação

A ideia é simples, para string s1e s2, tentamos compensar s2por i:

s1: abcabcabc
s2: bcabcab

Quando o deslocamento é 3:

s1: abcabcabc
s2:    bcabcab

Em seguida, para cada deslocamento i, realizamos uma verificação dinâmica de programação em s1[i:]e s2. Para cada um j, f[j, 0]seja o comprimento máximo dtal que s1[j - d:j] == s2[j - i - d: j - i]. Da mesma forma, f[j, 1]seja o comprimento máximo dtal que as strings s1[j - d:j]e s2[j - i - d:j - i]diferam no máximo 1 caractere.

Então s1[j] == s2[j - i], temos:

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]

De outra forma:

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])

E:

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

Como precisamos apenas de f [j - 1,:] para calcular f [j,:], apenas O (1) espaço extra é usado.

Por fim, o comprimento máximo será:

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

Código

#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;
}
Raio
fonte
Desculpe, estive analisando o código e não consigo descobrir como ele leva em consideração a possibilidade de correspondência de strings, exceto um caractere, como no exemplo "apple" e "aplle". Você poderia explicar?
rorlork
@rcrmn É isso que a parte da programação dinâmica está fazendo. Para entender, é útil tentar calcular f [j, 0] ef [j, 1] manualmente para alguns casos simples. O código anterior tem alguns erros, então eu atualizei a postagem.
Raio
Obrigado por isso. Você acha que também pode haver uma solução O (n log n)?
2

C ++

Tentei pensar em um bom algoritmo para fazer isso, mas hoje estou um pouco distraído e não consegui pensar em nada que funcionaria bem. Isso é executado no horário O (n ^ 3), portanto é muito lento. A outra opção em que pensei poderia ter sido teoricamente mais rápida, mas ocuparia espaço O (n ^ 2) e, com entradas de 1M, teria sido ainda pior.

É vergonhoso, leva 190 segundos para entradas de 15K. Vou tentar melhorar. Edit: Adicionado multiprocessamento. Agora leva 37 segundos para entrada de 15K em 8 threads.

#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
fonte
Lamento muito que seja uma solução tão ruim. Eu tenho procurado por um algoritmo para fazer isso no melhor momento, mas não encontrou nada por agora ...
rorlork
Pois bem, a complexidade da tarefa desejada deve ser cerca de O (n ^ 4) para o (n ^ 5), de modo que tempos de longo prazo são um dado
hoffmale
Eu acredito que deveria ser mais parecido com O (n ^ 3) na pior das hipóteses, pelo menos com o meu algoritmo. De qualquer forma, tenho certeza de que algo pode ser feito para melhorá-lo, como algum tipo de pesquisa em árvore, mas não tenho certeza de como isso seria implementado.
rorlork
Ah sim, O (n ^ 3) é ... tinha uma abordagem diferente em mente que levaria O (n ^ 4), mas essa é meio inútil agora xD
hoffmale
você pode economizar um pouco de tempo se alterar a verificação nos dois loops externos de i < a.length()para i < a.length - (longestA.second - longestA.first)(o mesmo para j e b.length ()), pois não será necessário processar nenhuma correspondência menor que a atual mais longa
hoffmale
2

R

Parece que eu estava complicando demais o problema com a solução anterior. Isso é cerca de 50% mais rápido (23 segundos em 15k strings) do que o anterior e bastante simples.

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)

Isso nunca será um candidato devido ao idioma, mas eu me diverti um pouco fazendo isso.
Não tenho certeza da complexidade, mas em algumas cordas de ~ 15k são necessários 43 segundos usando um único encadeamento. A maior parte disso foi a classificação das matrizes. Eu tentei algumas outras bibliotecas, mas sem melhorias significativas.

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)

Método:

  • Crie uma matriz de sufixos para cada sequência
  • Encomende as matrizes de sufixo
  • Percorra cada uma das matrizes de uma maneira escalonada, comparando o início de cada
MickyT
fonte
Obviamente, a solução mais fácil em R é usar o Biocondutor.
archaephyrryx
@archaephyrryx Uma solução de biocondutor seria divertida.
Seria ... Mas minha leitura rápida dos documentos estava muito acima da minha cabeça. Talvez se eu entendesse os termos :-)
MickyT
Eu apaguei meu primeiro comentário. Obviamente, você pode usar qualquer biblioteca de código aberto que desejar para esse desafio.