Trouver des correspondances tout sauf un


18

Ce défi consiste à écrire du code pour résoudre le problème suivant.

Étant donné deux chaînes A et B, votre code doit afficher les indices de début et de fin d'une sous-chaîne de A avec les propriétés suivantes.

  • La sous-chaîne de A doit également correspondre à une sous-chaîne de B avec jusqu'à une substitution d'un seul caractère dans la chaîne.
  • Il ne devrait plus y avoir de sous-chaîne de A qui satisfait la première propriété.

Par exemple:

A = xxxappleyyyyyyy

B = zapllezzz

La sous-chaîne appleavec des indices 4 8(indexation à partir de 1) serait une sortie valide.

But

Le score de votre réponse sera la somme de la longueur de votre code en octets + du temps en secondes qu'il faut à mon ordinateur lorsqu'il est exécuté sur les chaînes A et B de 1 million de longueur chacune.

Test et saisie

Je vais exécuter votre code sur deux chaînes de 1 million de long extraites des chaînes de http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

L'entrée sera en standard dans et sera simplement deux chaînes, séparées par une nouvelle ligne.

Langues et bibliothèques

Vous pouvez utiliser n'importe quelle langue disposant d'un compilateur / interprète / etc disponible gratuitement. pour Linux et toutes les bibliothèques qui sont également open source et librement disponibles pour Linux.

Ma machine Les chronomètres seront exécutés sur ma machine. Il s'agit d'une installation ubuntu standard sur un processeur AMD FX-8350 à huit cœurs. Cela signifie également que je dois pouvoir exécuter votre code. Par conséquent, n'utilisez que des logiciels gratuits facilement disponibles et veuillez inclure des instructions complètes sur la façon de compiler et d'exécuter votre code.


Vous avez besoin d'une définition de score plus absolue. Le temps d'exécution sur votre ordinateur ne semble pas être une bonne méthode de notation.
mbomb007

7
@ mbomb007 C'est la seule façon raisonnable de mesurer la vitesse du code et c'est celle qui est toujours utilisée dans les compétitions de code les plus rapides sur PPCG! Les gens affichent normalement leur score sur leur propre ordinateur dans leur réponse et attendent que l'OP produise ensuite un score définitif. Il est au moins 100% sans ambiguïté.

5
@ mbomb007 qui est une méthode de notation très largement utilisée pour le code le plus rapide.
Optimizer

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

2
@FryAmTheEggman Dans le cas très improbable d'une égalité, la première réponse l'emporte. appleya besoin de deux substitutions pour correspondre apllez. Peut-être que vous avez manqué que c'est apllen B et non appl?

Réponses:


4

Temps C ++: O (n ^ 2), espace supplémentaire: O (1)

Il faut 0,2 seconde pour terminer les 15 000 données sur ma machine.

Pour le compiler, utilisez:

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

Pour l'exécuter, utilisez:

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

Explication

L'idée est simple, pour string s1et s2, on essaie de compenser s2par i:

s1: abcabcabc
s2: bcabcab

Lorsque le décalage est de 3:

s1: abcabcabc
s2:    bcabcab

Ensuite, pour chaque décalage i, nous effectuons un balayage de programmation dynamique sur s1[i:]et s2. Pour chacun j, f[j, 0]soit la longueur maximale dtelle que s1[j - d:j] == s2[j - i - d: j - i]. De même, f[j, 1]soit la longueur maximale dtelle que les chaînes s1[j - d:j]et s2[j - i - d:j - i]diffèrent d'au plus 1 caractère.

Donc pour s1[j] == s2[j - i], nous avons:

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]

Autrement:

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

Et:

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

Puisque nous n'avons besoin que de f [j - 1,:] pour calculer f [j,:], seul l'espace supplémentaire O (1) est utilisé.

Enfin, la longueur maximale sera:

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

Code

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

Désolé, j'ai regardé le code et je ne trouve pas comment il prend en compte la possibilité de faire correspondre des chaînes sauf pour un caractère, comme dans l'exemple "pomme" et "aplle". Pourriez-vous expliquer?
rorlork

@rcrmn C'est ce que fait la partie de programmation dynamique. Pour comprendre, il est utile d'essayer de calculer manuellement f [j, 0] et f [j, 1] pour certains cas simples. Le code précédent a quelques bugs, j'ai donc mis à jour le message.
Ray

Merci pour ça. Pensez-vous qu'il pourrait également y avoir une solution O (n log n)?

2

C ++

J'ai essayé de penser à un bon algorithme pour le faire, mais je suis un peu distrait aujourd'hui et je ne pouvais penser à rien qui pourrait bien fonctionner. Cela fonctionne au temps O (n ^ 3), donc c'est très lent. L'autre option à laquelle j'ai pensé aurait pu être théoriquement plus rapide mais aurait pris de l'espace O (n ^ 2), et avec des entrées de 1M, cela aurait été pire encore.

C'est honteux, cela prend 190 secondes pour des entrées de 15K. Je vais essayer de l'améliorer. Edit: Ajout du multiprocessing. Prend maintenant 37 secondes pour une entrée de 15 Ko sur 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;
}

Je suis vraiment désolé que ce soit une si mauvaise solution. Je cherchais un algorithme pour accomplir cela dans un meilleur temps, mais je n'ai rien trouvé pour l'instant ...
rorlork

Eh bien, la complexité de la tâche requise devrait se situer entre O (n ^ 4) et O (n ^ 5), de sorte que les temps d'exécution longs sont une donnée
hoffmale

Je pense que cela devrait ressembler davantage à O (n ^ 3) dans le pire des cas, du moins avec mon algorithme. Quoi qu'il en soit, je suis sûr que quelque chose peut être fait pour l'améliorer, comme une sorte de recherche dans les arbres, mais je ne sais pas comment cela serait mis en œuvre.
rorlork

Oh oui, O (n ^ 3) c'est ... avait une approche différente à l'esprit qui aurait pris O (n ^ 4), mais celle-ci est un peu inutile maintenant xD
hoffmale

vous pourriez économiser un peu de temps si vous modifiez le contrôle dans les deux boucles pour extérieur à partir i < a.length()de i < a.length - (longestA.second - longestA.first)(même pour j et b.length ()) puisque vous aurez pas besoin de traiter tous les matchs plus petit que votre plus long cours d' un
hoffmale

2

R

Il me semble que je compliquais trop le problème avec la solution précédente. C'est environ 50% plus rapide (23 secondes sur des chaînes de 15k) que la précédente et assez simple.

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)

Ce ne sera jamais un concurrent à cause de la langue, mais j'ai eu un peu de plaisir à le faire.
Pas sûr de la complexité de celui-ci, mais sur quelques ~ 15k chaînes, cela prend 43 secondes en utilisant un seul thread. La plus grande partie de cela était le tri des tableaux. J'ai essayé d'autres bibliothèques, mais sans amélioration significative.

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éthode:

  • Créez un tableau de suffixes pour chaque chaîne
  • Commandez les tableaux de suffixes
  • Parcourez chacun des tableaux d'une manière échelonnée en comparant le début de chaque

Bien sûr, la solution la plus simple dans R est d'utiliser le bioconducteur.
archaephyrryx

@archaephyrryx Une solution bioconductrice serait amusante.

Ce serait ... Mais ma lecture rapide des documents était bien au-dessus de ma tête. Peut-être que si j'ai compris les termes :-)
MickyT

J'ai supprimé mon premier commentaire. Vous pouvez bien sûr utiliser n'importe quelle bibliothèque open source que vous aimez pour ce défi.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.