Sottostringa comune più lunga in tempo lineare


45

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.
  • Non dovrebbe essere più presente una sottostringa di A che soddisfi la prima proprietà.

Per esempio:

A = xxxappleyyyyyyy

B = zapplezzz

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

Funzionalità

Puoi presumere che l'input sarà su standard in o in un file nella directory locale, che è la tua scelta. Il formato del file sarà semplicemente due stringhe, separate da una nuova riga. La risposta dovrebbe essere un programma completo e non solo una funzione.

Vorrei infine testare il tuo codice su due sottostringhe tratte dalle stringhe in http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .

Punto

Questo è il code-golf con una svolta. Il codice deve essere eseguito in O(n)tempo, dove nè la lunghezza totale dell'input.

Lingue e biblioteche

Puoi usare qualsiasi lingua che abbia un compilatore / interprete / ecc. Liberamente disponibile. per Linux. Utilizzare solo librerie open source standard non progettate per risolvere questo compito. In caso di controversia, conterò questo come qualsiasi libreria che viene fornita di serie nella tua lingua o che puoi installare in una macchina Ubuntu predefinita da un repository predefinito.

Informazioni utili

Esistono almeno due modi per risolvere questo problema in tempo lineare. Uno è calcolare prima l'albero dei suffissi e il secondo è prima calcolare l'array di suffissi e l'array LCP.


4
O(n) timeSei sicuro che sia possibile?
Savenkov Alexey,

17
@Lembik Siamo spiacenti, ma questi sono algoritmi molto complessi e non è davvero divertente fare il gol di oltre 100 righe di codice.
FUZxxl

4
L'articolo al secondo link fornito sotto "Informazioni utili" dice che "costruire [un albero di suffisso] richiede tempo O (N ^ 2)"
KSFT

3
@Lembik Dovresti solo fare la domanda [codice più veloce], dove vince il programma con il miglior caso peggiore in notazione big-oh. Quindi otterrai almeno alcune risposte, e anche se qualcuno può risolverlo in O (n), vinceranno.
mbomb007,

9
Questa deve essere la domanda con le risposte più cancellate per risposta valida ...
FlipTack,

Risposte:


39

Python 2, 646 byte

G=range;w=raw_input;z=L,m,h=[0]*3
s=w();t=len(s);s+='!%s#'%w();u=len(s);I=z*u
def f(s,n):
 def r(o):
    b=[[]for _ in s];c=[]
    for x in B[:N]:b[s[x+o]]+=x,
    map(c.extend,b);B[:N]=c
 M=N=n--~n/3;t=n%3%2;B=G(n+t);del B[::3];r(2);u=m=p=r(1)>r(0);N-=n/3
 for x in B*1:v=s[x:x+3];m+=u<v;u=v;B[x/3+x%3/2*N]=m
 A=1/M*z or f(B+z,M)+z;B=[x*3for x in A if x<N];J=I[r(0):n];C=G(n)
 for k in C:b=A[t]/N;a=i,j=A[t]%N*3-~b,B[p];q=p<N<(s[i:i-~b],J[i/3+b+N-b*N])>(s[j+t/M:j-~b],J[j/3+b*N]);C[k]=x=a[q];I[x]=k;p+=q;t+=1-q
 return C
S=f(map(ord,s)+z*40,u)
for i in G(u):
 h-=h>0;j=S[I[i]-1]
 while s[i+h]==s[j+h]:h+=1
 if(i<t)==(t<j)<=h>m:m=h;L=min(i,j)
print-~L,L+m

Questo utilizza l'algoritmo di inclinazione descritto in "Costruzione di array di suffissi di lavoro lineare semplice" di Kärkkäinen e Sanders. L'implementazione del C ++ inclusa in quel documento sembra già un po '"impertinente", ma c'è ancora molto spazio per renderla più breve. Ad esempio, possiamo ricorrere fino ad arrivare a un array di lunghezza uno, anziché cortocircuitare come nel documento, senza violare il O(n)requisito.

Per la parte LCP, ho seguito "Calcolo del prefisso lineare più lungo nel tempo lineare nelle matrici di suffissi e nelle sue applicazioni" di Kusai et al.

Il programma viene emesso 1 0se la sottostringa comune più lunga è vuota.

Ecco un codice di sviluppo che include una versione precedente del programma che segue un po 'più da vicino l'implementazione del C ++, alcuni approcci più lenti per il confronto e un semplice generatore di casi di test:

from random import *

def brute(a,b):
    L=R=m=0

    for i in range(len(a)):
        for j in range(i+m+1,len(a)+1):
            if a[i:j] in b:
                m=j-i
                L,R=i,j

    return L+1,R

def suffix_array_slow(s):
    S=[]
    for i in range(len(s)):
        S+=[(s[i:],i)]
    S.sort()
    return [x[1] for x in S]

def slow1(a,b):
    # slow suffix array, slow lcp

    s=a+'!'+b
    S=suffix_array_slow(s)

    L=R=m=0

    for i in range(1,len(S)):
        x=S[i-1]
        y=S[i]
        p=s[x:]+'#'
        q=s[y:]+'$'
        h=0
        while p[h]==q[h]:
            h+=1
        if h>m and len(a)==sorted([x,y,len(a)])[1]:
            m=h
            L=min(x,y)
            R=L+h

    return L+1,R

def verify(a,b,L,R):
    if L<1 or R>len(a) or a[L-1:R] not in b:
        return 0
    LL,RR=brute(a,b)
    return R-L==RR-LL

def rand_string():
    if randint(0,1):
        n=randint(0,8)
    else:
        n=randint(0,24)
    a='zyxwvutsrq'[:randint(1,10)]
    s=''
    for _ in range(n):
        s+=choice(a)
    return s

def stress_test(f):
    numtrials=2000
    for trial in range(numtrials):
        a=rand_string()
        b=rand_string()
        L,R=f(a,b)
        if not verify(a,b,L,R):
            LL,RR=brute(a,b)
            print 'failed on',(a,b)
            print 'expected:',LL,RR
            print 'actual:',L,R
            return
    print 'ok'

def slow2(a,b):
    # slow suffix array, linear lcp

    s=a+'!'+b+'#'
    S=suffix_array_slow(s)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

def suffix_array(s,K):
    # skew algorithm

    n=len(s)
    s+=[0]*3
    n0=(n+2)/3
    n1=(n+1)/3
    n2=n/3
    n02=n0+n2
    adj=n0-n1

    def radix_pass(a,o,n=n02):
        c=[0]*(K+3)
        for x in a[:n]:
            c[s[x+o]+1]+=1
        for i in range(K+3):
            c[i]+=c[i-1]
        for x in a[:n]:
            j=s[x+o]
            a[c[j]]=x
            c[j]+=1

    A=[x for x in range(n+adj) if x%3]+[0]*3

    radix_pass(A,2)
    radix_pass(A,1)
    radix_pass(A,0)

    B=[0]*n02
    t=m=0

    for x in A[:n02]:
        u=s[x:x+3]
        m+=t<u
        t=u
        B[x/3+x%3/2*n0]=m

    A[:n02]=1/n02*[0]or suffix_array(B,m)
    I=A*1
    for i in range(n02):
        I[A[i]]=i+1

    B=[3*x for x in A if x<n0]
    radix_pass(B,0,n0)

    R=[]

    p=0
    t=adj
    while t<n02:
        x=A[t]
        b=x>=n0
        i=(x-b*n0)*3-~b
        j=B[p]
        if p==n0 or ((s[i:i+2],I[A[t]-n0+1])<(s[j:j+2],I[j/3+n0]) if b else (s[i],I[A[t]+n0])<(s[j],I[j/3])):R+=i,;t+=1
        else:R+=j,;p+=1

    return R+B[p:n0]

def solve(a,b):
    # linear

    s=a+'!'+b+'#'
    S=suffix_array(map(ord,s),128)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

stress_test(solve)

1
Correggimi se sbaglio, ma in realtà non sono 739 byte? Ho copiato in mothereff.in/byte-counter e cancellato 2 spazi dalle righe 6-9, ma non sono sicuro che sia corretto.
Patrick Roberts,

2
@PatrickRoberts Quelle sono schede.
Mitch Schwartz,

2
Bella risposta! Potresti dare un'occhiata a GSACA un nuovo tempo lineare SACA dal 2016. L'implementazione di riferimento è di 246 righe piene di commenti (170 senza commenti) e sembra molto giocabile. Lo troverai su github.
Christoph,

1
@MitchSchwartz Attualmente sto cercando di rimanere su noPMO, quindi non riesco a provare forti emozioni in questo momento (probabilmente a causa di sostanze chimiche cerebrali squilibrate). Al momento della lettura rapida del codice, il mio motore di golf sintattico lo ha individuato e non ricordo di aver provato emozioni specifiche. Hai pensato alla stessa cosa o perché la domanda? :) Ora sono curioso.
Yytsi,

1
@TuukkaX Questa è una risposta interessante che non mi aspettavo. Bene, non sono sicuro che dovrei esprimerlo in un modo speciale, ma il fatto che il tuo commento originale non fosse effettivamente corretto ha contribuito al motivo per cui ho deciso di chiedere. :)
Mitch Schwartz,
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.