Trovare tutte le partite tranne una


18

Questa sfida riguarda la scrittura di codice per risolvere il seguente problema.

Date due stringhe A e B, il codice dovrebbe generare gli indici iniziale e finale di una sottostringa di A con le seguenti proprietà.

  • La sottostringa di A dovrebbe anche corrispondere ad una sottostringa di B con fino a una sostituzione di un singolo carattere nella stringa.
  • Non dovrebbe essere più presente una sottostringa di A che soddisfi la prima proprietà.

Per esempio:

A = xxxappleyyyyyyy

B = zapllezzz

La sottostringa applecon indici 4 8(indicizzazione da 1) sarebbe un output valido.

Punto

Il punteggio della tua risposta sarà la somma della lunghezza del tuo codice in byte + il tempo in secondi che impiega il mio computer quando viene eseguito sulle stringhe A e B di lunghezza 1 milione ciascuna.

Test e input

Eseguirò il tuo codice su due stringhe di lunghezza 1 milione prese dalle stringhe in http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

L'input sarà su standard in e sarà semplicemente due stringhe, separate da una nuova riga.

Lingue e biblioteche

Puoi usare qualsiasi lingua che abbia un compilatore / interprete / ecc. Liberamente disponibile. per Linux e tutte le librerie che sono anche open source e disponibili gratuitamente per Linux.

La mia macchina I tempi verranno eseguiti sulla mia macchina. Questa è un'installazione ubuntu standard su un processore a otto core AMD FX-8350. Questo significa anche che devo essere in grado di eseguire il tuo codice. Di conseguenza, utilizzare solo software gratuito facilmente disponibile e includere istruzioni complete su come compilare ed eseguire il codice.


È necessaria una definizione di punteggio più assoluta. Il tempo di esecuzione sul tuo computer non sembra un buon metodo di punteggio.
mbomb007,

7
@ mbomb007 È l'unico modo sensato per misurare la velocità del codice ed è quello sempre usato nelle competizioni di codice più veloci su PPCG! Le persone normalmente pubblicano il proprio punteggio sul proprio computer nella risposta e attendono che l'OP produca un punteggio definitivo. Almeno al 100% non è ambiguo.

5
@ mbomb007 che è un metodo di punteggio molto utilizzato per il codice più veloce.
Ottimizzatore,

if(hash(str1 == test1 && str2 == test2)) print("100,150") else ..-- pensieri?
John Dvorak,

2
@FryAmTheEggman Nel caso molto improbabile di un pareggio, vince la prima risposta. appleyha bisogno di due sostituzioni per abbinare apllez. Forse ti sei perso che è apllin B e non appl?

Risposte:


4

Tempo C ++: O (n ^ 2), spazio extra: O (1)

Sono necessari 0,2 secondi per completare i dati di 15.000 sulla mia macchina.

Per compilarlo, utilizzare:

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

Per eseguirlo, utilizzare:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

spiegazione

L'idea è semplice, per stringa s1e s2, proviamo a compensare s2con i:

s1: abcabcabc
s2: bcabcab

Quando offset è 3:

s1: abcabcabc
s2:    bcabcab

Quindi, per ogni offset i, eseguiamo una scansione di programmazione dinamica su s1[i:]e s2. Per ciascuno j, f[j, 0]sia la lunghezza massima dtale che s1[j - d:j] == s2[j - i - d: j - i]. Allo stesso modo, f[j, 1]sia la lunghezza massima in modo dtale che le stringhe s1[j - d:j]e s2[j - i - d:j - i]differiscano al massimo da 1 carattere.

Quindi per s1[j] == s2[j - i], abbiamo:

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]

Altrimenti:

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 

Poiché abbiamo solo bisogno di f [j - 1,:] per calcolare f [j,:], viene utilizzato solo O (1) spazio aggiuntivo.

Infine, la lunghezza massima sarà:

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

Codice

#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;
}

Spiacenti, ho esaminato il codice e non riesco a trovare il modo in cui tiene conto della possibilità della corrispondenza delle stringhe ad eccezione di un carattere, come nell'esempio "mela" e "aplle". Potresti spiegare?
rorlork,

@rcrmn Ecco cosa fa la parte di programmazione dinamica. Per capire, è utile provare a calcolare manualmente f [j, 0] ef [j, 1] per alcuni casi semplici. Il codice precedente ha alcuni bug, quindi ho aggiornato il post.
Ray

Grazie per questo. Pensi che potrebbe esserci anche una soluzione O (n log n)?

2

C ++

Ho provato a pensare a un buon algoritmo per farlo, ma oggi sono un po 'distratto e non riesco a pensare a qualcosa che funzioni bene. Funziona a O (n ^ 3), quindi è molto lento. L'altra opzione a cui ho pensato avrebbe potuto essere teoricamente più veloce ma avrebbe preso spazio O (n ^ 2) e, con input di 1M, sarebbe stata anche peggio.

È vergognoso, ci vogliono 190 secondi per input di 15K. Proverò a migliorarlo. Modifica: aggiunto multiprocessing. Ora impiega 37 secondi per l'input 15K su 8 thread.

#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;
}

Mi dispiace davvero che sia una brutta soluzione. Ho cercato un algoritmo per ottenere questo risultato in un momento migliore, ma per ora non ho trovato nulla ...
rorlork

Bene, la complessità dell'attività richiesta dovrebbe essere
compresa

Credo che dovrebbe essere più simile a O (n ^ 3) nel peggiore dei casi, almeno con il mio algoritmo. Ad ogni modo, sono sicuro che si può fare qualcosa per migliorarlo, come una specie di ricerca dell'albero, ma non sono sicuro di come verrà implementato.
rorlork,

Oh sì, O (n ^ 3) è ... aveva in mente un approccio diverso che avrebbe preso O (n ^ 4), ma quello è un po 'inutile ormai xD
hoffmale

potresti risparmiare un po 'di tempo se modifichi il segno di spunta nei due esterni per i loop da i < a.length()a i < a.length - (longestA.second - longestA.first)(uguale per j e b.length ()) poiché non dovrai elaborare alcuna corrispondenza più piccola di quella più lunga attuale
hoffmale

2

R

Sembra che stia finendo per complicare il problema con la soluzione precedente. Questo è circa il 50% più veloce (23 secondi su stringhe da 15k) rispetto al precedente e piuttosto semplice.

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)

Questo non sarà mai un contendente a causa della lingua, ma mi sono divertito un po 'a farlo.
Non sono sicuro della complessità, ma su un paio di stringhe ~ 15k ci vogliono 43 secondi usando un singolo thread. La maggior parte di ciò era lo smistamento delle matrici. Ho provato alcune altre librerie, ma senza miglioramenti significativi.

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)

Metodo:

  • Creare un array di suffissi per ogni stringa
  • Ordinare le matrici di suffissi
  • Scorri ciascuno degli array in modo sfalsato confrontando l'inizio di ciascuno

Naturalmente, la soluzione più semplice in R è usare il bioconduttore.
archaephyrryx,

@archaephyrryx Una soluzione di bioconduttore sarebbe divertente.

Sarebbe ... Ma la mia rapida lettura dei documenti era ben oltre la mia testa. Forse se capissi i termini :-)
MickyT

Ho cancellato il mio primo commento. Puoi ovviamente usare qualsiasi libreria open source che ti piace per questa sfida.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.